feat: Integrate forum layout support across various components and enhance room handling logic
This commit is contained in:
306
src/app/features/space/SpaceOptionsMenu.tsx
Normal file
306
src/app/features/space/SpaceOptionsMenu.tsx
Normal file
@@ -0,0 +1,306 @@
|
||||
import React, { forwardRef, useState } from 'react';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Box, Icon, Icons, Line, Menu, MenuItem, Text, config, toRem } from 'folds';
|
||||
import type { HierarchyItem } from '../../hooks/useSpaceHierarchy';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal';
|
||||
import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal';
|
||||
import { AddExistingModal } from '../add-existing';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
import { usePowerLevels } from '../../hooks/usePowerLevels';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||
import { useOpenSpaceSettings } from '../../state/hooks/spaceSettings';
|
||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||
import { useMarkRoomsAsRead } from '../../hooks/useMarkAsRead';
|
||||
import { useRecursiveChildScopeFactory, useSpaceChildren } from '../../state/hooks/roomList';
|
||||
import { allRoomsAtom } from '../../state/room-list/roomList';
|
||||
import { roomToParentsAtom } from '../../state/room/roomToParents';
|
||||
import { useRoomsUnread } from '../../state/hooks/unread';
|
||||
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
||||
import { UseStateProvider } from '../../components/UseStateProvider';
|
||||
import { LeaveSpacePrompt } from '../../components/leave-space-prompt';
|
||||
import { InviteUserPrompt } from '../../components/invite-user-prompt';
|
||||
import { copyToClipboard } from '../../utils/dom';
|
||||
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
|
||||
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
||||
import { getViaServers } from '../../plugins/via-servers';
|
||||
|
||||
type ForumAddOptions = {
|
||||
item: HierarchyItem;
|
||||
showAddSpace: boolean;
|
||||
};
|
||||
|
||||
export type OpenAddExistingOptions = {
|
||||
parentId: string;
|
||||
space: boolean;
|
||||
};
|
||||
|
||||
type SpaceOptionsMenuProps = {
|
||||
room: Room;
|
||||
requestClose: () => void;
|
||||
forumAdd?: ForumAddOptions;
|
||||
/** Render Add Existing outside the popout (forum kebab). */
|
||||
onOpenAddExisting?: (options: OpenAddExistingOptions) => void;
|
||||
};
|
||||
|
||||
export const SpaceOptionsMenu = forwardRef<HTMLDivElement, SpaceOptionsMenuProps>(
|
||||
({ room, requestClose, forumAdd, onOpenAddExisting }, ref) => {
|
||||
const mx = useMatrixClient();
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
const [developerTools] = useSetting(settingsAtom, 'developerTools');
|
||||
const roomToParents = useAtomValue(roomToParentsAtom);
|
||||
const powerLevels = usePowerLevels(room);
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
const canInvite = permissions.action('invite', mx.getSafeUserId());
|
||||
const openSpaceSettings = useOpenSpaceSettings();
|
||||
const { navigateRoom } = useRoomNavigate();
|
||||
const markRoomsAsRead = useMarkRoomsAsRead(mx);
|
||||
|
||||
const [invitePrompt, setInvitePrompt] = useState(false);
|
||||
const [addExistingRoom, setAddExistingRoom] = useState(false);
|
||||
const [addExistingSpace, setAddExistingSpace] = useState(false);
|
||||
const openCreateRoomModal = useOpenCreateRoomModal();
|
||||
const openCreateSpaceModal = useOpenCreateSpaceModal();
|
||||
|
||||
const allChild = useSpaceChildren(
|
||||
allRoomsAtom,
|
||||
room.roomId,
|
||||
useRecursiveChildScopeFactory(mx, roomToParents)
|
||||
);
|
||||
const unread = useRoomsUnread(allChild, roomToUnreadAtom);
|
||||
|
||||
const handleMarkAsRead = () => {
|
||||
markRoomsAsRead(allChild, hideActivity);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
const handleCopyLink = () => {
|
||||
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
||||
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
|
||||
copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers));
|
||||
requestClose();
|
||||
};
|
||||
|
||||
const handleInvite = () => {
|
||||
setInvitePrompt(true);
|
||||
};
|
||||
|
||||
const handleRoomSettings = () => {
|
||||
openSpaceSettings(room.roomId);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
const handleOpenTimeline = () => {
|
||||
navigateRoom(room.roomId);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
const handleAddTopic = () => {
|
||||
if (!forumAdd) return;
|
||||
openCreateRoomModal(forumAdd.item.roomId);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
const handleAddCategory = () => {
|
||||
if (!forumAdd) return;
|
||||
openCreateSpaceModal(forumAdd.item.roomId);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
const handleAddExistingTopic = () => {
|
||||
if (!forumAdd) return;
|
||||
if (onOpenAddExisting) {
|
||||
onOpenAddExisting({ parentId: forumAdd.item.roomId, space: false });
|
||||
requestClose();
|
||||
return;
|
||||
}
|
||||
setAddExistingRoom(true);
|
||||
};
|
||||
|
||||
const handleAddExistingCategory = () => {
|
||||
if (!forumAdd) return;
|
||||
if (onOpenAddExisting) {
|
||||
onOpenAddExisting({ parentId: forumAdd.item.roomId, space: true });
|
||||
requestClose();
|
||||
return;
|
||||
}
|
||||
setAddExistingSpace(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
|
||||
{forumAdd && (
|
||||
<>
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
<MenuItem
|
||||
onClick={handleAddTopic}
|
||||
variant="Primary"
|
||||
fill="None"
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Plus} />}
|
||||
radii="300"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Add Topic
|
||||
</Text>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleAddExistingTopic}
|
||||
size="300"
|
||||
radii="300"
|
||||
fill="None"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Add Existing Topic
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{forumAdd.showAddSpace && (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={handleAddCategory}
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Plus} />}
|
||||
radii="300"
|
||||
fill="None"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Add Section
|
||||
</Text>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleAddExistingCategory}
|
||||
size="300"
|
||||
radii="300"
|
||||
fill="None"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Add Existing Section
|
||||
</Text>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
<Line variant="Surface" size="300" />
|
||||
</>
|
||||
)}
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
{invitePrompt && (
|
||||
<InviteUserPrompt
|
||||
room={room}
|
||||
requestClose={() => {
|
||||
setInvitePrompt(false);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
onClick={handleMarkAsRead}
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.CheckTwice} />}
|
||||
radii="300"
|
||||
disabled={!unread}
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Mark as Read
|
||||
</Text>
|
||||
</MenuItem>
|
||||
</Box>
|
||||
<Line variant="Surface" size="300" />
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
<MenuItem
|
||||
onClick={handleInvite}
|
||||
variant="Primary"
|
||||
fill="None"
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.UserPlus} />}
|
||||
radii="300"
|
||||
aria-pressed={invitePrompt}
|
||||
disabled={!canInvite}
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Invite
|
||||
</Text>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleCopyLink}
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Link} />}
|
||||
radii="300"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Copy Link
|
||||
</Text>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleRoomSettings}
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Setting} />}
|
||||
radii="300"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Space Settings
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{developerTools && (
|
||||
<MenuItem
|
||||
onClick={handleOpenTimeline}
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Terminal} />}
|
||||
radii="300"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Event Timeline
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Box>
|
||||
<Line variant="Surface" size="300" />
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
<UseStateProvider initial={false}>
|
||||
{(promptLeave, setPromptLeave) => (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => setPromptLeave(true)}
|
||||
variant="Critical"
|
||||
fill="None"
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.ArrowGoLeft} />}
|
||||
radii="300"
|
||||
aria-pressed={promptLeave}
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Leave Space
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{promptLeave && (
|
||||
<LeaveSpacePrompt
|
||||
roomId={room.roomId}
|
||||
onDone={requestClose}
|
||||
onCancel={() => setPromptLeave(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</UseStateProvider>
|
||||
</Box>
|
||||
</Menu>
|
||||
{forumAdd && addExistingRoom && (
|
||||
<AddExistingModal parentId={forumAdd.item.roomId} requestClose={() => setAddExistingRoom(false)} />
|
||||
)}
|
||||
{forumAdd && addExistingSpace && (
|
||||
<AddExistingModal
|
||||
space
|
||||
parentId={forumAdd.item.roomId}
|
||||
requestClose={() => setAddExistingSpace(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user