feat(call): add docked call panel and remote cursor overlay

- Implemented DockedCallPanel component to render the docked call interface when an active call is present.
- Created RemoteCursorOverlay component to display remote cursor positions during screen sharing.
- Added hooks for managing docked call state and remote cursor functionality.
- Introduced pending drag state management for seamless transitions between docked and floating states.
- Developed embed filter management components for personal and room-wide settings, allowing users to customize URL embed visibility.
- Implemented auto-joining functionality for rooms within spaces, enhancing user experience during space navigation.
- Added state management for tracking joining progress of rooms in spaces.
This commit is contained in:
2026-02-19 17:49:48 +11:00
parent bfbdf98468
commit 3f6f2134ad
42 changed files with 3801 additions and 219 deletions

View File

@@ -18,6 +18,7 @@ import {
import { useVirtualizer } from '@tanstack/react-virtual';
import { useAtom, useAtomValue } from 'jotai';
import FocusTrap from 'focus-trap-react';
import { Room } from 'matrix-js-sdk';
import { factoryRoomIdByActivity, factoryRoomIdByAtoZ } from '../../../utils/sort';
import {
NavButton,
@@ -64,44 +65,73 @@ import {
} from '../../../hooks/useRoomsNotificationPreferences';
import { UseStateProvider } from '../../../components/UseStateProvider';
import { JoinAddressPrompt } from '../../../components/join-address-prompt';
import { ManageRoomsPrompt } from '../../../components/manage-rooms-prompt';
import { _RoomSearchParams } from '../../paths';
type HomeMenuProps = {
requestClose: () => void;
onManageRooms: () => void;
};
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
const orphanRooms = useHomeRooms();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const mx = useMatrixClient();
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(
({ requestClose, onManageRooms }, ref) => {
const orphanRooms = useHomeRooms();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const mx = useMatrixClient();
const handleMarkAsRead = () => {
if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
requestClose();
};
const handleMarkAsRead = () => {
if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
requestClose();
};
return (
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<MenuItem
onClick={handleMarkAsRead}
size="300"
after={<Icon size="100" src={Icons.CheckTwice} />}
radii="300"
aria-disabled={!unread}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Mark as Read
</Text>
</MenuItem>
</Box>
</Menu>
);
});
const handleManageRooms = () => {
requestClose();
onManageRooms();
};
return (
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<MenuItem
onClick={handleMarkAsRead}
size="300"
after={<Icon size="100" src={Icons.CheckTwice} />}
radii="300"
aria-disabled={!unread}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Mark as Read
</Text>
</MenuItem>
<MenuItem
onClick={handleManageRooms}
size="300"
after={<Icon size="100" src={Icons.Setting} />}
radii="300"
aria-disabled={orphanRooms.length === 0}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Manage Rooms
</Text>
</MenuItem>
</Box>
</Menu>
);
}
);
function HomeHeader() {
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
const [showManageRooms, setShowManageRooms] = useState(false);
const mx = useMatrixClient();
const orphanRooms = useHomeRooms();
const rooms = useMemo(() => {
return orphanRooms
.map((roomId) => mx.getRoom(roomId))
.filter((room): room is Room => room !== null);
}, [mx, orphanRooms]);
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
const cords = evt.currentTarget.getBoundingClientRect();
@@ -144,10 +174,20 @@ function HomeHeader() {
escapeDeactivates: stopPropagation,
}}
>
<HomeMenu requestClose={() => setMenuAnchor(undefined)} />
<HomeMenu
requestClose={() => setMenuAnchor(undefined)}
onManageRooms={() => setShowManageRooms(true)}
/>
</FocusTrap>
}
/>
{showManageRooms && (
<ManageRoomsPrompt
rooms={rooms}
onDone={() => setShowManageRooms(false)}
onCancel={() => setShowManageRooms(false)}
/>
)}
</>
);
}