diff --git a/web/chat/thread-menu.react.js b/web/chat/thread-menu.react.js
index e7cdcc3f8..d64483960 100644
--- a/web/chat/thread-menu.react.js
+++ b/web/chat/thread-menu.react.js
@@ -1,250 +1,263 @@
// @flow
import * as React from 'react';
import {
leaveThread,
leaveThreadActionTypes,
} from 'lib/actions/thread-actions';
import { usePromoteSidebar } from 'lib/hooks/promote-sidebar.react';
import { childThreadInfos } from 'lib/selectors/thread-selectors';
import {
threadHasPermission,
viewerIsMember,
threadIsChannel,
} from 'lib/shared/thread-utils';
import {
type ThreadInfo,
threadTypes,
threadPermissions,
} from 'lib/types/thread-types';
import {
useServerCall,
useDispatchActionPromise,
} from 'lib/utils/action-utils';
import { isDev } from 'lib/utils/dev-utils';
import MenuItem from '../components/menu-item.react';
import Menu from '../components/menu.react';
import SidebarListModal from '../modals/chat/sidebar-list-modal.react';
+import SidebarPromoteModal from '../modals/chat/sidebar-promote-modal.react';
import { useModalContext } from '../modals/modal-provider.react';
import ConfirmLeaveThreadModal from '../modals/threads/confirm-leave-thread-modal.react';
import ThreadMembersModal from '../modals/threads/members/members-modal.react';
import SubchannelsModal from '../modals/threads/subchannels/subchannels-modal.react';
import ThreadSettingsModal from '../modals/threads/thread-settings-modal.react';
import { useSelector } from '../redux/redux-utils';
import SWMansionIcon from '../SWMansionIcon.react';
import css from './thread-menu.css';
type ThreadMenuProps = {
+threadInfo: ThreadInfo,
};
function ThreadMenu(props: ThreadMenuProps): React.Node {
const { pushModal, popModal } = useModalContext();
const { threadInfo } = props;
const { onPromoteSidebar } = usePromoteSidebar(threadInfo);
const onClickSettings = React.useCallback(
() => pushModal(),
[pushModal, threadInfo.id],
);
const settingsItem = React.useMemo(() => {
return (
);
}, [onClickSettings]);
const onClickMembers = React.useCallback(
() =>
pushModal(
,
),
[popModal, pushModal, threadInfo.id],
);
const membersItem = React.useMemo(() => {
if (threadInfo.type === threadTypes.PERSONAL) {
return null;
}
return (
);
}, [onClickMembers, threadInfo.type]);
const childThreads = useSelector(
state => childThreadInfos(state)[threadInfo.id],
);
const hasSidebars = React.useMemo(() => {
return childThreads?.some(
childThreadInfo => childThreadInfo.type === threadTypes.SIDEBAR,
);
}, [childThreads]);
const onClickSidebars = React.useCallback(
() => pushModal(),
[pushModal, threadInfo],
);
const sidebarItem = React.useMemo(() => {
if (!hasSidebars) {
return null;
}
return (
);
}, [hasSidebars, onClickSidebars]);
const canCreateSubchannels = React.useMemo(
() => threadHasPermission(threadInfo, threadPermissions.CREATE_SUBCHANNELS),
[threadInfo],
);
const hasSubchannels = React.useMemo(() => {
return !!childThreads?.some(threadIsChannel);
}, [childThreads]);
const onClickViewSubchannels = React.useCallback(
() =>
pushModal(
,
),
[popModal, pushModal, threadInfo.id],
);
const viewSubchannelsItem = React.useMemo(() => {
if (!hasSubchannels) {
return null;
}
return (
);
}, [hasSubchannels, onClickViewSubchannels]);
const createSubchannelsItem = React.useMemo(() => {
if (!canCreateSubchannels) {
return null;
}
return (
);
}, [canCreateSubchannels]);
const dispatchActionPromise = useDispatchActionPromise();
const callLeaveThread = useServerCall(leaveThread);
const onConfirmLeaveThread = React.useCallback(() => {
dispatchActionPromise(
leaveThreadActionTypes,
callLeaveThread(threadInfo.id),
);
popModal();
}, [callLeaveThread, popModal, dispatchActionPromise, threadInfo.id]);
const onClickLeaveThread = React.useCallback(
() =>
pushModal(
,
),
[popModal, onConfirmLeaveThread, pushModal, threadInfo],
);
const leaveThreadItem = React.useMemo(() => {
const canLeaveThread = threadHasPermission(
threadInfo,
threadPermissions.LEAVE_THREAD,
);
if (!viewerIsMember(threadInfo) || !canLeaveThread) {
return null;
}
return (
);
}, [onClickLeaveThread, threadInfo]);
+ const onClickPromoteSidebarToThread = React.useCallback(
+ () =>
+ pushModal(
+ ,
+ ),
+ [pushModal, threadInfo, popModal, onPromoteSidebar],
+ );
+
const promoteSidebar = React.useMemo(() => {
return (
);
- }, [onPromoteSidebar]);
+ }, [onClickPromoteSidebarToThread]);
const menuItems = React.useMemo(() => {
const notificationsItem = (
);
const separator =
;
// TODO: Enable menu items when the modals are implemented
const SHOW_NOTIFICATIONS = false;
const SHOW_CREATE_SUBCHANNELS = false;
const SHOW_PROMOTE_SIDEBAR = isDev;
const items = [
settingsItem,
SHOW_NOTIFICATIONS && notificationsItem,
membersItem,
sidebarItem,
viewSubchannelsItem,
SHOW_CREATE_SUBCHANNELS && createSubchannelsItem,
leaveThreadItem && separator,
SHOW_PROMOTE_SIDEBAR && promoteSidebar,
leaveThreadItem,
];
return items.filter(Boolean);
}, [
settingsItem,
membersItem,
sidebarItem,
viewSubchannelsItem,
promoteSidebar,
createSubchannelsItem,
leaveThreadItem,
]);
const icon = React.useMemo(
() => ,
[],
);
return ;
}
export default ThreadMenu;
diff --git a/web/modals/chat/sidebar-promote-modal.css b/web/modals/chat/sidebar-promote-modal.css
new file mode 100644
index 000000000..3dca55379
--- /dev/null
+++ b/web/modals/chat/sidebar-promote-modal.css
@@ -0,0 +1,16 @@
+div.modal_body {
+ padding: 20px;
+ color: var(--fg);
+ background-color: var(--modal-bg);
+ border-radius: 16px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+div.buttonContainer {
+ padding-top: 32px;
+ display: flex;
+ gap: 24px;
+ justify-content: flex-end;
+}
diff --git a/web/modals/chat/sidebar-promote-modal.react.js b/web/modals/chat/sidebar-promote-modal.react.js
new file mode 100644
index 000000000..9268e37d1
--- /dev/null
+++ b/web/modals/chat/sidebar-promote-modal.react.js
@@ -0,0 +1,49 @@
+// @flow
+
+import * as React from 'react';
+
+import type { ThreadInfo } from 'lib/types/thread-types';
+
+import Button from '../../components/button.react';
+import Modal from '../modal.react';
+import css from './sidebar-promote-modal.css';
+
+type Props = {
+ +onClose: () => void,
+ +onConfirm: () => void,
+ +threadInfo: ThreadInfo,
+};
+
+function SidebarPromoteModal(props: Props): React.Node {
+ const { threadInfo, onClose, onConfirm } = props;
+ const { uiName } = threadInfo;
+
+ const handleConfirm = React.useCallback(() => {
+ onConfirm();
+ onClose();
+ }, [onClose, onConfirm]);
+
+ return (
+
+
+
{`Are you sure you want to promote "${uiName}"?`}
+
Promoting a sidebar to a full thread cannot be undone.
+
+
+
+
+
+
+ );
+}
+
+export default SidebarPromoteModal;