mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-02-23 21:53:05 +01:00
add room header menu
This commit is contained in:
parent
fb642087f7
commit
d3dd37dc3b
1 changed files with 251 additions and 90 deletions
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { MouseEventHandler, forwardRef, useState } from 'react';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
@ -12,8 +12,16 @@ import {
|
||||||
Icons,
|
Icons,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
toRem,
|
||||||
|
config,
|
||||||
|
Line,
|
||||||
|
PopOut,
|
||||||
|
RectCords,
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { Room } from 'matrix-js-sdk';
|
||||||
|
|
||||||
import { useStateEvent } from '../../hooks/useStateEvent';
|
import { useStateEvent } from '../../hooks/useStateEvent';
|
||||||
import { PageHeader } from '../../components/page';
|
import { PageHeader } from '../../components/page';
|
||||||
|
@ -28,16 +36,138 @@ import { useRoom } from '../../hooks/useRoom';
|
||||||
import { useSetSetting } from '../../state/hooks/settings';
|
import { useSetSetting } from '../../state/hooks/settings';
|
||||||
import { settingsAtom } from '../../state/settings';
|
import { settingsAtom } from '../../state/settings';
|
||||||
import { useSpaceOptionally } from '../../hooks/useSpace';
|
import { useSpaceOptionally } from '../../hooks/useSpace';
|
||||||
import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
|
import {
|
||||||
|
getHomeSearchPath,
|
||||||
|
getOriginBaseUrl,
|
||||||
|
getSpaceSearchPath,
|
||||||
|
joinPathComponent,
|
||||||
|
withOriginBaseUrl,
|
||||||
|
withSearchParam,
|
||||||
|
} from '../../pages/pathUtils';
|
||||||
import { getCanonicalAliasOrRoomId } from '../../utils/matrix';
|
import { getCanonicalAliasOrRoomId } from '../../utils/matrix';
|
||||||
import { _SearchPathSearchParams } from '../../pages/paths';
|
import { _SearchPathSearchParams } from '../../pages/paths';
|
||||||
import * as css from './RoomViewHeader.css';
|
import * as css from './RoomViewHeader.css';
|
||||||
|
import { useRoomUnread } from '../../state/hooks/unread';
|
||||||
|
import { usePowerLevelsAPI } from '../../hooks/usePowerLevels';
|
||||||
|
import { markAsRead } from '../../../client/action/notifications';
|
||||||
|
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
||||||
|
import { openInviteUser } from '../../../client/action/navigation';
|
||||||
|
import { copyToClipboard } from '../../utils/dom';
|
||||||
|
|
||||||
|
type RoomMenuProps = {
|
||||||
|
room: Room;
|
||||||
|
linkPath: string;
|
||||||
|
requestClose: () => void;
|
||||||
|
};
|
||||||
|
const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(
|
||||||
|
({ room, linkPath, requestClose }, ref) => {
|
||||||
|
const mx = useMatrixClient();
|
||||||
|
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
||||||
|
const { getPowerLevel, canDoAction } = usePowerLevelsAPI();
|
||||||
|
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
|
||||||
|
|
||||||
|
const handleMarkAsRead = () => {
|
||||||
|
markAsRead(room.roomId);
|
||||||
|
requestClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInvite = () => {
|
||||||
|
openInviteUser(room.roomId);
|
||||||
|
requestClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopyLink = () => {
|
||||||
|
copyToClipboard(withOriginBaseUrl(getOriginBaseUrl(), linkPath));
|
||||||
|
requestClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRoomSettings = () => {
|
||||||
|
alert('Work In Progress...');
|
||||||
|
requestClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLeaveRoom = () => {
|
||||||
|
mx.leave(room.roomId);
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
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>
|
||||||
|
Room Settings
|
||||||
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
</Box>
|
||||||
|
<Line variant="Surface" size="300" />
|
||||||
|
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
|
<MenuItem
|
||||||
|
onClick={handleLeaveRoom}
|
||||||
|
variant="Critical"
|
||||||
|
fill="None"
|
||||||
|
size="300"
|
||||||
|
after={<Icon size="100" src={Icons.ArrowGoLeft} />}
|
||||||
|
radii="300"
|
||||||
|
>
|
||||||
|
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||||
|
Leave Room
|
||||||
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
</Box>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export function RoomViewHeader() {
|
export function RoomViewHeader() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const space = useSpaceOptionally();
|
const space = useSpaceOptionally();
|
||||||
|
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||||
|
|
||||||
const encryptionEvent = useStateEvent(room, StateEvent.RoomEncryption);
|
const encryptionEvent = useStateEvent(room, StateEvent.RoomEncryption);
|
||||||
const ecryptedRoom = !!encryptionEvent;
|
const ecryptedRoom = !!encryptionEvent;
|
||||||
|
@ -45,6 +175,8 @@ export function RoomViewHeader() {
|
||||||
const topic = topicEvent?.getContent().topic as string | undefined | null;
|
const topic = topicEvent?.getContent().topic as string | undefined | null;
|
||||||
|
|
||||||
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
|
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
|
||||||
|
const location = useLocation();
|
||||||
|
const currentPath = joinPathComponent(location);
|
||||||
|
|
||||||
const handleSearchClick = () => {
|
const handleSearchClick = () => {
|
||||||
const searchParams: _SearchPathSearchParams = {
|
const searchParams: _SearchPathSearchParams = {
|
||||||
|
@ -56,8 +188,13 @@ export function RoomViewHeader() {
|
||||||
navigate(withSearchParam(path, searchParams));
|
navigate(withSearchParam(path, searchParams));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||||
|
setMenuAnchor(evt.currentTarget.getBoundingClientRect());
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
|
<Box grow="Yes" gap="300">
|
||||||
<Box grow="Yes" alignItems="Center" gap="300">
|
<Box grow="Yes" alignItems="Center" gap="300">
|
||||||
<Avatar size="300">
|
<Avatar size="300">
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
|
@ -153,11 +290,35 @@ export function RoomViewHeader() {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(triggerRef) => (
|
{(triggerRef) => (
|
||||||
<IconButton ref={triggerRef}>
|
<IconButton onClick={handleOpenMenu} ref={triggerRef} aria-pressed={!!menuAnchor}>
|
||||||
<Icon size="400" src={Icons.VerticalDots} />
|
<Icon size="400" src={Icons.VerticalDots} filled={!!menuAnchor} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
<PopOut
|
||||||
|
anchor={menuAnchor}
|
||||||
|
position="Bottom"
|
||||||
|
align="End"
|
||||||
|
content={
|
||||||
|
<FocusTrap
|
||||||
|
focusTrapOptions={{
|
||||||
|
initialFocus: false,
|
||||||
|
returnFocusOnDeactivate: false,
|
||||||
|
onDeactivate: () => setMenuAnchor(undefined),
|
||||||
|
clickOutsideDeactivates: true,
|
||||||
|
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
|
||||||
|
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RoomMenu
|
||||||
|
room={room}
|
||||||
|
linkPath={currentPath}
|
||||||
|
requestClose={() => setMenuAnchor(undefined)}
|
||||||
|
/>
|
||||||
|
</FocusTrap>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue