diff --git a/web/chat/thread-menu.react.js b/web/chat/thread-menu.react.js index 56338580e..c1bd22682 100644 --- a/web/chat/thread-menu.react.js +++ b/web/chat/thread-menu.react.js @@ -1,366 +1,370 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import uuid from 'uuid'; import { leaveThreadActionTypes, useLeaveThread, } from 'lib/actions/thread-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/swmansion-icon.react.js'; import { usePromoteSidebar } from 'lib/hooks/promote-sidebar.react.js'; import { childThreadInfos } from 'lib/selectors/thread-selectors.js'; import { type OutboundDMOperationSpecification, dmOperationSpecificationTypes, } from 'lib/shared/dm-ops/dm-op-utils.js'; import { useProcessAndSendDMOperation } from 'lib/shared/dm-ops/process-dm-ops.js'; import { threadIsChannel, useThreadHasPermission, viewerIsMember, } from 'lib/shared/thread-utils.js'; import { type DMLeaveThreadOperation } from 'lib/types/dm-ops.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import { - threadTypes, + threadTypeIsPersonal, + threadTypeIsPrivate, threadTypeIsSidebar, threadTypeIsThick, thickThreadTypes, } from 'lib/types/thread-types-enum.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import css from './thread-menu.css'; import MenuItem from '../components/menu-item.react.js'; import Menu from '../components/menu.react.js'; import SidebarPromoteModal from '../modals/chat/sidebar-promote-modal.react.js'; import ConfirmLeaveThreadModal from '../modals/threads/confirm-leave-thread-modal.react.js'; import ComposeSubchannelModal from '../modals/threads/create/compose-subchannel-modal.react.js'; import ThreadSettingsMediaGalleryModal from '../modals/threads/gallery/thread-settings-media-gallery.react.js'; import ThreadMembersModal from '../modals/threads/members/members-modal.react.js'; import ThreadNotificationsModal from '../modals/threads/notifications/notifications-modal.react.js'; import ThreadSettingsModal from '../modals/threads/settings/thread-settings-modal.react.js'; import SidebarsModal from '../modals/threads/sidebars/sidebars-modal.react.js'; import SubchannelsModal from '../modals/threads/subchannels/subchannels-modal.react.js'; import { useSelector } from '../redux/redux-utils.js'; type ThreadMenuProps = { +threadInfo: ThreadInfo, }; function ThreadMenu(props: ThreadMenuProps): React.Node { const { pushModal, popModal } = useModalContext(); const { threadInfo } = props; const { onPromoteSidebar, canPromoteSidebar } = 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.GENESIS_PERSONAL) { + if ( + threadTypeIsPersonal(threadInfo.type) || + threadTypeIsPrivate(threadInfo.type) + ) { return null; } return ( ); }, [onClickMembers, threadInfo.type]); const onClickThreadMediaGallery = React.useCallback( () => pushModal( , ), [popModal, pushModal, threadInfo], ); const threadMediaGalleryItem = React.useMemo(() => { if (threadTypeIsThick(threadInfo.type)) { return null; } return ( ); }, [onClickThreadMediaGallery, threadInfo.type]); const childThreads = useSelector( state => childThreadInfos(state)[threadInfo.id], ); const hasSidebars = React.useMemo(() => { return childThreads?.some(childThreadInfo => threadTypeIsSidebar(childThreadInfo.type), ); }, [childThreads]); const onClickSidebars = React.useCallback( () => pushModal(), [popModal, pushModal, threadInfo.id], ); const sidebarItem = React.useMemo(() => { if (!hasSidebars) { return null; } return ( ); }, [hasSidebars, onClickSidebars]); const canCreateSubchannels = useThreadHasPermission( threadInfo, threadPermissions.CREATE_SUBCHANNELS, ); 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 onClickCreateSubchannel = React.useCallback( () => pushModal( , ), [popModal, pushModal, threadInfo], ); const createSubchannelsItem = React.useMemo(() => { if (!canCreateSubchannels) { return null; } return ( ); }, [canCreateSubchannels, onClickCreateSubchannel]); const dispatchActionPromise = useDispatchActionPromise(); const callLeaveThread = useLeaveThread(); const processAndSendDMOperation = useProcessAndSendDMOperation(); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const onConfirmLeaveThread = React.useCallback(() => { if (threadTypeIsThick(threadInfo.type)) { invariant(viewerID, 'viewerID should be set'); const op: DMLeaveThreadOperation = { type: 'leave_thread', editorID: viewerID, time: Date.now(), messageID: uuid.v4(), threadID: threadInfo.id, }; const opSpecification: OutboundDMOperationSpecification = { type: dmOperationSpecificationTypes.OUTBOUND, op, recipients: { type: 'all_thread_members', threadID: threadInfo.type === thickThreadTypes.THICK_SIDEBAR && threadInfo.parentThreadID ? threadInfo.parentThreadID : threadInfo.id, }, }; void processAndSendDMOperation(opSpecification); } else { void dispatchActionPromise( leaveThreadActionTypes, callLeaveThread({ threadID: threadInfo.id }), ); } popModal(); }, [ threadInfo.type, threadInfo.id, threadInfo.parentThreadID, popModal, viewerID, processAndSendDMOperation, dispatchActionPromise, callLeaveThread, ]); const onClickLeaveThread = React.useCallback( () => pushModal( , ), [popModal, onConfirmLeaveThread, pushModal, threadInfo], ); const canLeaveThread = useThreadHasPermission( threadInfo, threadPermissions.LEAVE_THREAD, ); const leaveThreadItem = React.useMemo(() => { if (!viewerIsMember(threadInfo) || !canLeaveThread) { return null; } return ( ); }, [onClickLeaveThread, threadInfo, canLeaveThread]); const onClickPromoteSidebarToThread = React.useCallback( () => pushModal( , ), [pushModal, threadInfo, popModal, onPromoteSidebar], ); const promoteSidebar = React.useMemo(() => { return ( ); }, [onClickPromoteSidebarToThread]); const onClickNotifications = React.useCallback(() => { pushModal( , ); }, [popModal, pushModal, threadInfo.id]); const notificationsItem = React.useMemo(() => { if (!viewerIsMember(threadInfo)) { return null; } return ( ); }, [onClickNotifications, threadInfo]); const menuItems = React.useMemo(() => { const separator =
; const items = [ settingsItem, notificationsItem, membersItem, threadMediaGalleryItem, sidebarItem, viewSubchannelsItem, createSubchannelsItem, leaveThreadItem && separator, canPromoteSidebar && promoteSidebar, leaveThreadItem, ]; return items.filter(Boolean); }, [ settingsItem, notificationsItem, membersItem, threadMediaGalleryItem, sidebarItem, viewSubchannelsItem, promoteSidebar, createSubchannelsItem, leaveThreadItem, canPromoteSidebar, ]); const icon = React.useMemo( () => , [], ); return {menuItems}; } export default ThreadMenu;