feat: Integrate forum layout support across various components and enhance room handling logic
This commit is contained in:
@@ -43,6 +43,8 @@ import { ClientBindAtoms, ClientLayout, ClientRoot } from './client';
|
||||
import { Home, HomeRouteRoomProvider, HomeSearch } from './client/home';
|
||||
import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct';
|
||||
import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space';
|
||||
import { ForumAwareSpaceLayout } from '../features/forum/ForumAwareSpaceLayout';
|
||||
import { ForumSpaceLayout } from '../features/forum/ForumSpaceLayout';
|
||||
import { Explore, FeaturedRooms, PublicRooms } from './client/explore';
|
||||
import { Notifications, Inbox, Invites } from './client/inbox';
|
||||
import { setAfterLoginRedirectPath } from './afterLoginRedirectPath';
|
||||
@@ -236,15 +238,9 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
||||
path={SPACE_PATH}
|
||||
element={
|
||||
<RouteSpaceProvider>
|
||||
<PageRoot
|
||||
nav={
|
||||
<MobileFriendlyPageNav path={SPACE_PATH}>
|
||||
<Space />
|
||||
</MobileFriendlyPageNav>
|
||||
}
|
||||
>
|
||||
<AnimatedOutlet />
|
||||
</PageRoot>
|
||||
<ForumSpaceLayout>
|
||||
<ForumAwareSpaceLayout />
|
||||
</ForumSpaceLayout>
|
||||
</RouteSpaceProvider>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { mDirectAtom } from '../../../state/mDirectList';
|
||||
import { roomToParentsAtom } from '../../../state/room/roomToParents';
|
||||
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
||||
import { useOrphanRooms } from '../../../state/hooks/roomList';
|
||||
import { StateEvent } from '../../../../types/matrix/room';
|
||||
import { RoomType, StateEvent } from '../../../../types/matrix/room';
|
||||
|
||||
export const useHomeRooms = () => {
|
||||
const mx = useMatrixClient();
|
||||
@@ -15,6 +15,25 @@ export const useHomeRooms = () => {
|
||||
|
||||
// Filter out rooms that are sub-rooms of ANY room (not just orphan rooms)
|
||||
const rooms = useMemo(() => {
|
||||
const isSpaceLikeRoom = (roomId: string): boolean => {
|
||||
const room = mx.getRoom(roomId);
|
||||
if (!room) return false;
|
||||
|
||||
if (typeof room.isSpaceRoom === 'function' && room.isSpaceRoom()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const roomKindEvent = room.currentState.getStateEvents(StateEvent.PaarrotRoomKind, '');
|
||||
const roomKind = roomKindEvent?.getContent<{ kind?: string }>()?.kind;
|
||||
if (roomKind === 'forum_space') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const createEvent = room.currentState.getStateEvents(StateEvent.RoomCreate, '');
|
||||
const roomType = createEvent?.getContent<{ type?: string }>()?.type;
|
||||
return roomType === RoomType.Space || roomType === RoomType.Forum;
|
||||
};
|
||||
|
||||
// Build a set of all sub-room IDs from ALL joined rooms
|
||||
const allSubRoomIds = new Set<string>();
|
||||
mx.getRooms().forEach((room) => {
|
||||
@@ -24,7 +43,7 @@ export const useHomeRooms = () => {
|
||||
});
|
||||
|
||||
// Return only rooms that are not sub-rooms of another room
|
||||
return orphanRooms.filter((roomId) => !allSubRoomIds.has(roomId));
|
||||
return orphanRooms.filter((roomId) => !allSubRoomIds.has(roomId) && !isSpaceLikeRoom(roomId));
|
||||
}, [mx, orphanRooms]);
|
||||
|
||||
return rooms;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { IsDirectRoomProvider, RoomProvider } from '../../../hooks/useRoom';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { JoinBeforeNavigate } from '../../../features/join-before-navigate';
|
||||
import { useSpace } from '../../../hooks/useSpace';
|
||||
import { getAllParents, getSpaceChildren } from '../../../utils/room';
|
||||
import { getAllParents, getSpaceChildren, isSpace } from '../../../utils/room';
|
||||
import { roomToParentsAtom } from '../../../state/room/roomToParents';
|
||||
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
||||
import { useSearchParamsViaServers } from '../../../hooks/router/useSearchParamsViaServers';
|
||||
@@ -38,7 +38,7 @@ export function SpaceRouteRoomProvider({ children }: { children: ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
if (developerTools && room.isSpaceRoom() && room.roomId === space.roomId) {
|
||||
if (developerTools && isSpace(room) && room.roomId === space.roomId) {
|
||||
// allow to view space timeline
|
||||
return (
|
||||
<RoomProvider key={room.roomId} value={room}>
|
||||
|
||||
@@ -85,165 +85,8 @@ import { ContainerColor } from '../../../styles/ContainerColor.css';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||
import { BreakWord } from '../../../styles/Text.css';
|
||||
import { InviteUserPrompt } from '../../../components/invite-user-prompt';
|
||||
|
||||
type SpaceMenuProps = {
|
||||
room: Room;
|
||||
requestClose: () => void;
|
||||
};
|
||||
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClose }, 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 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();
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
{invitePrompt && room && (
|
||||
<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>
|
||||
);
|
||||
});
|
||||
import { isSpace, shouldShowForumLobby } from '../../../utils/room';
|
||||
import { SpaceOptionsMenu } from '../../../features/space/SpaceOptionsMenu';
|
||||
|
||||
function SpaceHeader() {
|
||||
const space = useSpace();
|
||||
@@ -298,7 +141,7 @@ function SpaceHeader() {
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<SpaceMenu room={space} requestClose={() => setMenuAnchor(undefined)} />
|
||||
<SpaceOptionsMenu room={space} requestClose={() => setMenuAnchor(undefined)} />
|
||||
</FocusTrap>
|
||||
}
|
||||
/>
|
||||
@@ -455,7 +298,7 @@ export function Space() {
|
||||
|
||||
const addSubRooms = (parentRoomId: string, baseDepth: number) => {
|
||||
const room = mx.getRoom(parentRoomId);
|
||||
if (!room || room.isSpaceRoom()) return;
|
||||
if (!room || isSpace(room)) return;
|
||||
|
||||
const subRoomsEvent = room.currentState.getStateEvents(StateEvent.PaarrotSubRooms, '');
|
||||
const subRooms = subRoomsEvent?.getContent<{ children: string[] }>()?.children ?? [];
|
||||
@@ -484,7 +327,7 @@ export function Space() {
|
||||
|
||||
// Add sub-rooms after each non-space room
|
||||
const room = mx.getRoom(entry.roomId);
|
||||
if (room && !room.isSpaceRoom()) {
|
||||
if (room && !isSpace(room)) {
|
||||
addSubRooms(entry.roomId, 0);
|
||||
}
|
||||
});
|
||||
@@ -506,6 +349,10 @@ export function Space() {
|
||||
const getToLink = (roomId: string) =>
|
||||
getSpaceRoomPath(spaceIdOrAlias, getCanonicalAliasOrRoomId(mx, roomId));
|
||||
|
||||
if (shouldShowForumLobby(space)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageNav>
|
||||
<SpaceHeader />
|
||||
@@ -616,7 +463,7 @@ export function Space() {
|
||||
const room = mx.getRoom(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
if (room.isSpaceRoom()) {
|
||||
if (isSpace(room)) {
|
||||
const categoryId = makeNavCategoryId(space.roomId, roomId);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user