feat: enhance update notification layout and add room info display in developer tools

This commit is contained in:
2026-02-24 21:49:03 +11:00
parent 495747af84
commit 58966aec19
5 changed files with 359 additions and 51 deletions

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import {
Box,
Text,
@@ -11,7 +11,14 @@ import {
MenuItem,
config,
color,
TextArea,
Dialog,
Overlay,
OverlayBackdrop,
OverlayCenter,
Modal,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { Page, PageContent, PageHeader } from '../../../components/page';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css';
@@ -30,6 +37,10 @@ import {
AccountDataSubmitCallback,
} from '../../../components/AccountDataEditor';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { stopPropagation } from '../../../utils/keyboard';
import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { isSpace } from '../../../utils/room';
type DeveloperToolsProps = {
requestClose: () => void;
@@ -41,6 +52,8 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
const roomState = useRoomState(room);
const accountData = useRoomAccountData(room);
const powerLevels = usePowerLevels(room);
const creators = useRoomCreators(room);
const [expandState, setExpandState] = useState(false);
const [expandStateType, setExpandStateType] = useState<string>();
@@ -49,6 +62,72 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
const [expandAccountData, setExpandAccountData] = useState(false);
const [accountDataType, setAccountDataType] = useState<string | null>();
const [showRoomInfo, setShowRoomInfo] = useState(false);
const roomInfoJson = useMemo(() => {
const liveTimeline = room.getLiveTimeline();
const liveEvents = liveTimeline.getEvents();
const oldestEvent = liveEvents.length > 0 ? liveEvents[0] : null;
const newestEvent = liveEvents.length > 0 ? liveEvents[liveEvents.length - 1] : null;
// Get power level tags
const powerLevelTagsEvent = room.currentState.getStateEvents('in.cinny.room.power_level_tags', '');
const powerLevelTags = powerLevelTagsEvent?.getContent();
const info: any = {
roomId: room.roomId,
name: room.name,
canonicalAlias: room.canonicalAlias,
altAliases: room.getAltAliases(),
isSpace: isSpace(room),
joinRule: room.getJoinRule(),
guestAccess: room.getGuestAccess(),
historyVisibility: room.getHistoryVisibility(),
myUserId: mx.getUserId(),
myMembership: room.getMyMembership(),
creator: Array.from(creators),
memberCount: room.getJoinedMemberCount(),
invitedMemberCount: room.getInvitedMemberCount(),
powerLevels,
powerLevelTags: powerLevelTags || null,
tags: room.tags,
notificationCounts: {
total: room.getRoomUnreadNotificationCount('total'),
highlight: room.getRoomUnreadNotificationCount('highlight'),
},
timeline: {
oldestEventTs: oldestEvent?.getTs(),
newestEventTs: newestEvent?.getTs(),
liveEventsCount: liveEvents.length,
timelineSetSize: room.getTimelineSets().length,
},
encryption: room.hasEncryptionStateEvent(),
state: {} as any,
accountData: {} as any,
};
// Add all state events
roomState.forEach((stateKeyToEvents, eventType) => {
info.state[eventType] = {};
stateKeyToEvents.forEach((event, stateKey) => {
info.state[eventType][stateKey || '_default'] = {
content: event.getContent(),
sender: event.getSender(),
stateKey: event.getStateKey(),
eventId: event.getId(),
timestamp: event.getTs(),
unsigned: event.getUnsigned(),
};
});
});
// Add account data
accountData.forEach((content, type) => {
info.accountData[type] = content;
});
return JSON.stringify(info, null, 2);
}, [room, mx, roomState, accountData, powerLevels, creators]);
const handleClose = useCallback(() => {
setOpenStateEvent(undefined);
@@ -82,6 +161,62 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
return <StateEventEditor {...openStateEvent} requestClose={handleClose} />;
}
if (showRoomInfo) {
return (
<Overlay open backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: () => setShowRoomInfo(false),
escapeDeactivates: stopPropagation,
}}
>
<Modal size="500" variant="Surface">
<Box direction="Column" gap="400" style={{ height: '80vh' }}>
<Box justifyContent="SpaceBetween" alignItems="Center" gap="200">
<Text size="H4" truncate>
{isSpace(room) ? 'Space' : 'Room'} Information
</Text>
<Box gap="200">
<Button
onClick={() => copyToClipboard(roomInfoJson)}
variant="Secondary"
fill="Soft"
size="300"
radii="300"
>
<Text size="B300">Copy All</Text>
</Button>
<IconButton onClick={() => setShowRoomInfo(false)} variant="Surface" size="300">
<Icon src={Icons.Cross} size="200" />
</IconButton>
</Box>
</Box>
<Box grow="Yes" style={{ minHeight: 0 }}>
<Scroll hideTrack visibility="Hover" size="300">
<TextArea
style={{
minHeight: '100%',
fontFamily: 'monospace',
fontSize: '12px',
resize: 'none',
}}
defaultValue={roomInfoJson}
readOnly
autoComplete="off"
onFocus={(e) => e.target.select()}
/>
</Scroll>
</Box>
</Box>
</Modal>
</FocusTrap>
</OverlayCenter>
</Overlay>
);
}
return (
<Page>
<PageHeader outlined={false}>
@@ -144,6 +279,23 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
</Button>
}
/>
<SettingTile
title={`View ${isSpace(room) ? 'Space' : 'Room'} Information`}
description="Display comprehensive information about this room/space including state, members, power levels, and more."
after={
<Button
onClick={() => setShowRoomInfo(true)}
variant="Primary"
fill="Soft"
size="300"
radii="300"
outlined
before={<Icon src={Icons.Info} size="50" />}
>
<Text size="B300">View Info</Text>
</Button>
}
/>
</SequenceCard>
)}
</Box>