fix: permissions and room icon resolution (#2)
* Initialize call state upon room creation for call rooms, remove subsequent useless permission * handle case of missing call permissions * use call icon for room item summary when room is call room * replace previous icon src resolution function with a more robust approach * replace usages of previous icon resolution function with new implementation * fix room name not updating for a while when changed * set up framework for room power level overrides upon room creation * override join call permission to all members upon room creation * fix broken usages of RoomIcon * remove unneeded import * remove unnecessary logic * format with prettier
This commit is contained in:
parent
9554b31c7d
commit
efb3e115db
15 changed files with 190 additions and 100 deletions
|
|
@ -11,6 +11,7 @@ import { CreateRoomKind } from './CreateRoomKindSelector';
|
||||||
import { RoomType, StateEvent } from '../../../types/matrix/room';
|
import { RoomType, StateEvent } from '../../../types/matrix/room';
|
||||||
import { getViaServers } from '../../plugins/via-servers';
|
import { getViaServers } from '../../plugins/via-servers';
|
||||||
import { getMxIdServer } from '../../utils/matrix';
|
import { getMxIdServer } from '../../utils/matrix';
|
||||||
|
import { IPowerLevels } from '../../hooks/usePowerLevels';
|
||||||
|
|
||||||
export const createRoomCreationContent = (
|
export const createRoomCreationContent = (
|
||||||
type: RoomType | undefined,
|
type: RoomType | undefined,
|
||||||
|
|
@ -82,6 +83,44 @@ export const createRoomEncryptionState = () => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const createRoomCallState = () => ({
|
||||||
|
type: 'org.matrix.msc3401.call',
|
||||||
|
state_key: '',
|
||||||
|
content: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createPowerLevelContentOverrides = (
|
||||||
|
base: IPowerLevels,
|
||||||
|
overrides: Partial<IPowerLevels>
|
||||||
|
): IPowerLevels => ({
|
||||||
|
...base,
|
||||||
|
...overrides,
|
||||||
|
...(base.events || overrides.events
|
||||||
|
? {
|
||||||
|
events: {
|
||||||
|
...base.events,
|
||||||
|
...overrides.events,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(base.users || overrides.users
|
||||||
|
? {
|
||||||
|
users: {
|
||||||
|
...base.users,
|
||||||
|
...overrides.users,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(base.notifications || overrides.notifications
|
||||||
|
? {
|
||||||
|
notifications: {
|
||||||
|
...base.notifications,
|
||||||
|
...overrides.notifications,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
|
||||||
export type CreateRoomData = {
|
export type CreateRoomData = {
|
||||||
version: string;
|
version: string;
|
||||||
type?: RoomType;
|
type?: RoomType;
|
||||||
|
|
@ -94,6 +133,7 @@ export type CreateRoomData = {
|
||||||
knock: boolean;
|
knock: boolean;
|
||||||
allowFederation: boolean;
|
allowFederation: boolean;
|
||||||
additionalCreators?: string[];
|
additionalCreators?: string[];
|
||||||
|
powerLevelContentOverrides?: IPowerLevels;
|
||||||
};
|
};
|
||||||
export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promise<string> => {
|
export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promise<string> => {
|
||||||
const initialState: ICreateRoomStateEvent[] = [];
|
const initialState: ICreateRoomStateEvent[] = [];
|
||||||
|
|
@ -106,6 +146,10 @@ export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promis
|
||||||
initialState.push(createRoomParentState(data.parent));
|
initialState.push(createRoomParentState(data.parent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.type === RoomType.Call) {
|
||||||
|
initialState.push(createRoomCallState());
|
||||||
|
}
|
||||||
|
|
||||||
initialState.push(createRoomJoinRulesState(data.kind, data.parent, data.knock));
|
initialState.push(createRoomJoinRulesState(data.kind, data.parent, data.knock));
|
||||||
|
|
||||||
const options: ICreateRoomOpts = {
|
const options: ICreateRoomOpts = {
|
||||||
|
|
@ -136,5 +180,15 @@ export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promis
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.powerLevelContentOverrides) {
|
||||||
|
const roomPowers = await mx.getStateEvent(result.room_id, StateEvent.RoomPowerLevels, '');
|
||||||
|
const updatedPowers = createPowerLevelContentOverrides(
|
||||||
|
roomPowers,
|
||||||
|
data.powerLevelContentOverrides
|
||||||
|
);
|
||||||
|
|
||||||
|
await mx.sendStateEvent(result.room_id, StateEvent.RoomPowerLevels as any, updatedPowers, '');
|
||||||
|
}
|
||||||
|
|
||||||
return result.room_id;
|
return result.room_id;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ export function RoomMentionAutocomplete({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<RoomIcon size="100" joinRule={room.getJoinRule()} space={room.isSpaceRoom()} />
|
<RoomIcon size="100" joinRule={room.getJoinRule()} roomType={room.getType()} />
|
||||||
)}
|
)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { JoinRule } from 'matrix-js-sdk';
|
||||||
import { AvatarFallback, AvatarImage, Icon, Icons, color } from 'folds';
|
import { AvatarFallback, AvatarImage, Icon, Icons, color } from 'folds';
|
||||||
import React, { ComponentProps, ReactEventHandler, ReactNode, forwardRef, useState } from 'react';
|
import React, { ComponentProps, ReactEventHandler, ReactNode, forwardRef, useState } from 'react';
|
||||||
import * as css from './RoomAvatar.css';
|
import * as css from './RoomAvatar.css';
|
||||||
import { joinRuleToIconSrc } from '../../utils/room';
|
import { getRoomIconSrc } from '../../utils/room';
|
||||||
import colorMXID from '../../../util/colorMXID';
|
import colorMXID from '../../../util/colorMXID';
|
||||||
|
|
||||||
type RoomAvatarProps = {
|
type RoomAvatarProps = {
|
||||||
|
|
@ -44,14 +44,10 @@ export function RoomAvatar({ roomId, src, alt, renderFallback }: RoomAvatarProps
|
||||||
export const RoomIcon = forwardRef<
|
export const RoomIcon = forwardRef<
|
||||||
SVGSVGElement,
|
SVGSVGElement,
|
||||||
Omit<ComponentProps<typeof Icon>, 'src'> & {
|
Omit<ComponentProps<typeof Icon>, 'src'> & {
|
||||||
joinRule: JoinRule;
|
joinRule?: JoinRule;
|
||||||
space?: boolean;
|
roomType?: string;
|
||||||
call?: boolean;
|
locked?: boolean;
|
||||||
}
|
}
|
||||||
>(({ joinRule, space, call, ...props }, ref) => (
|
>(({ joinRule, roomType, locked, ...props }, ref) => (
|
||||||
<Icon
|
<Icon src={getRoomIconSrc(Icons, roomType, joinRule, locked)} {...props} ref={ref} />
|
||||||
src={joinRuleToIconSrc(Icons, joinRule, space || false, call || false) ?? Icons.Hash}
|
|
||||||
{...props}
|
|
||||||
ref={ref}
|
|
||||||
/>
|
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Room } from 'matrix-js-sdk';
|
import { EventType, Room } from 'matrix-js-sdk';
|
||||||
import React, {
|
import React, {
|
||||||
useContext,
|
useContext,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
|
@ -21,6 +21,10 @@ import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||||
import { getMemberDisplayName } from '../../utils/room';
|
import { getMemberDisplayName } from '../../utils/room';
|
||||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
import { getMxIdLocalPart } from '../../utils/matrix';
|
||||||
import * as css from './CallView.css';
|
import * as css from './CallView.css';
|
||||||
|
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||||
|
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||||
|
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||||
|
import { useRoomName } from '../../hooks/useRoomMeta';
|
||||||
|
|
||||||
type OriginalStyles = {
|
type OriginalStyles = {
|
||||||
position?: string;
|
position?: string;
|
||||||
|
|
@ -57,6 +61,13 @@ export function CallView({ room }: { room: Room }) {
|
||||||
|
|
||||||
const [visibleCallNames, setVisibleCallNames] = useState('');
|
const [visibleCallNames, setVisibleCallNames] = useState('');
|
||||||
|
|
||||||
|
const powerLevels = usePowerLevelsContext();
|
||||||
|
const creators = useRoomCreators(room);
|
||||||
|
|
||||||
|
const roomName = useRoomName(room);
|
||||||
|
const permissions = useRoomPermissions(creators, powerLevels);
|
||||||
|
const canJoin = permissions.event(EventType.GroupCallMemberPrefix, mx.getSafeUserId());
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isActiveCallReady,
|
isActiveCallReady,
|
||||||
activeCallRoomId,
|
activeCallRoomId,
|
||||||
|
|
@ -160,6 +171,8 @@ export function CallView({ room }: { room: Room }) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleJoinVCClick: MouseEventHandler<HTMLElement> = (evt) => {
|
const handleJoinVCClick: MouseEventHandler<HTMLElement> = (evt) => {
|
||||||
|
if (!canJoin) return;
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
setViewedCallRoomId(room.roomId);
|
setViewedCallRoomId(room.roomId);
|
||||||
|
|
@ -210,11 +223,7 @@ export function CallView({ room }: { room: Room }) {
|
||||||
>
|
>
|
||||||
<CallViewUserGrid>
|
<CallViewUserGrid>
|
||||||
{callMembers.slice(0, 6).map((callMember) => (
|
{callMembers.slice(0, 6).map((callMember) => (
|
||||||
<CallViewUser
|
<CallViewUser key={callMember.membershipID} room={room} callMembership={callMember} />
|
||||||
key={callMember.membershipID}
|
|
||||||
room={room}
|
|
||||||
callMembership={callMember}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</CallViewUserGrid>
|
</CallViewUserGrid>
|
||||||
|
|
||||||
|
|
@ -231,21 +240,25 @@ export function CallView({ room }: { room: Room }) {
|
||||||
paddingBottom: config.space.S300,
|
paddingBottom: config.space.S300,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{room.name}
|
{roomName}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="T200">
|
<Text size="T200">
|
||||||
{visibleCallNames !== '' ? visibleCallNames : 'No one'}{' '}
|
{visibleCallNames !== '' ? visibleCallNames : 'No one'}{' '}
|
||||||
{memberDisplayNames.length > 1 ? 'are' : 'is'} currently in voice
|
{memberDisplayNames.length > 1 ? 'are' : 'is'} currently in voice
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Button variant="Secondary" disabled={isActiveCallRoom} onClick={handleJoinVCClick}>
|
<Button
|
||||||
|
variant="Secondary"
|
||||||
|
disabled={!canJoin || isActiveCallRoom}
|
||||||
|
onClick={handleJoinVCClick}
|
||||||
|
>
|
||||||
{isActiveCallRoom ? (
|
{isActiveCallRoom ? (
|
||||||
<Box justifyContent="Center" alignItems="Center" gap="200">
|
<Box justifyContent="Center" alignItems="Center" gap="200">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
<Text size="B500">{activeCallRoomId === room.roomId ? `Joining` : 'Join Voice'}</Text>
|
<Text size="B500">{activeCallRoomId === room.roomId ? `Joining` : 'Join Voice'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Text size="B500">Join Voice</Text>
|
<Text size="B500">{canJoin ? 'Join Voice' : 'Channel Locked'}</Text>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ export function RoomProfileEdit({
|
||||||
alt={name}
|
alt={name}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<RoomIcon
|
<RoomIcon
|
||||||
space={room.isSpaceRoom()}
|
roomType={room.getType()}
|
||||||
size="400"
|
size="400"
|
||||||
joinRule={joinRule?.join_rule ?? JoinRule.Invite}
|
joinRule={joinRule?.join_rule ?? JoinRule.Invite}
|
||||||
filled
|
filled
|
||||||
|
|
@ -342,7 +342,7 @@ export function RoomProfile({ permissions }: RoomProfileProps) {
|
||||||
alt={name}
|
alt={name}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<RoomIcon
|
<RoomIcon
|
||||||
space={room.isSpaceRoom()}
|
roomType={room.getType()}
|
||||||
size="400"
|
size="400"
|
||||||
joinRule={joinRule?.join_rule ?? JoinRule.Invite}
|
joinRule={joinRule?.join_rule ?? JoinRule.Invite}
|
||||||
filled
|
filled
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ import {
|
||||||
RoomVersionSelector,
|
RoomVersionSelector,
|
||||||
useAdditionalCreators,
|
useAdditionalCreators,
|
||||||
} from '../../components/create-room';
|
} from '../../components/create-room';
|
||||||
import { RoomType } from '../../../types/matrix/room';
|
import { RoomType, StateEvent } from '../../../types/matrix/room';
|
||||||
|
import { IPowerLevels } from '../../hooks/usePowerLevels';
|
||||||
|
|
||||||
const getCreateRoomKindToIcon = (kind: CreateRoomKind) => {
|
const getCreateRoomKindToIcon = (kind: CreateRoomKind) => {
|
||||||
if (kind === CreateRoomKind.Private) return Icons.HashLock;
|
if (kind === CreateRoomKind.Private) return Icons.HashLock;
|
||||||
|
|
@ -118,9 +119,18 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP
|
||||||
roomKnock = knock;
|
roomKnock = knock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let roomType;
|
||||||
|
const powerOverrides: IPowerLevels = {
|
||||||
|
events: {},
|
||||||
|
};
|
||||||
|
if (callRoom) {
|
||||||
|
roomType = RoomType.Call;
|
||||||
|
powerOverrides.events![StateEvent.GroupCallMemberPrefix] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
create({
|
create({
|
||||||
version: selectedRoomVersion,
|
version: selectedRoomVersion,
|
||||||
type: callRoom ? RoomType.Call : undefined,
|
type: roomType,
|
||||||
parent: space,
|
parent: space,
|
||||||
kind,
|
kind,
|
||||||
name: roomName,
|
name: roomName,
|
||||||
|
|
@ -130,6 +140,7 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP
|
||||||
knock: roomKnock,
|
knock: roomKnock,
|
||||||
allowFederation: federation,
|
allowFederation: federation,
|
||||||
additionalCreators: allowAdditionalCreators ? additionalCreators : undefined,
|
additionalCreators: allowAdditionalCreators ? additionalCreators : undefined,
|
||||||
|
powerLevelContentOverrides: powerOverrides,
|
||||||
}).then((roomId) => {
|
}).then((roomId) => {
|
||||||
if (alive()) {
|
if (alive()) {
|
||||||
onCreate?.(roomId);
|
onCreate?.(roomId);
|
||||||
|
|
@ -183,12 +194,7 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP
|
||||||
title="Call Room"
|
title="Call Room"
|
||||||
description="Enable this to create a room optimized for voice calls."
|
description="Enable this to create a room optimized for voice calls."
|
||||||
after={
|
after={
|
||||||
<Switch
|
<Switch variant="Primary" value={callRoom} onChange={setCallRoom} disabled={disabled} />
|
||||||
variant="Primary"
|
|
||||||
value={callRoom}
|
|
||||||
onChange={setCallRoom}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</SequenceCard>
|
</SequenceCard>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import { LocalRoomSummaryLoader } from '../../components/RoomSummaryLoader';
|
||||||
import { UseStateProvider } from '../../components/UseStateProvider';
|
import { UseStateProvider } from '../../components/UseStateProvider';
|
||||||
import { RoomTopicViewer } from '../../components/room-topic-viewer';
|
import { RoomTopicViewer } from '../../components/room-topic-viewer';
|
||||||
import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard';
|
import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard';
|
||||||
import { Membership } from '../../../types/matrix/room';
|
import { Membership, RoomType } from '../../../types/matrix/room';
|
||||||
import * as css from './RoomItem.css';
|
import * as css from './RoomItem.css';
|
||||||
import * as styleCss from './style.css';
|
import * as styleCss from './style.css';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||||
|
|
@ -175,6 +175,7 @@ function RoomProfileError({ roomId, suggested, inaccessibleRoom, via }: RoomProf
|
||||||
|
|
||||||
type RoomProfileProps = {
|
type RoomProfileProps = {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
|
roomType?: string;
|
||||||
name: string;
|
name: string;
|
||||||
topic?: string;
|
topic?: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
|
@ -185,6 +186,7 @@ type RoomProfileProps = {
|
||||||
};
|
};
|
||||||
function RoomProfile({
|
function RoomProfile({
|
||||||
roomId,
|
roomId,
|
||||||
|
roomType,
|
||||||
name,
|
name,
|
||||||
topic,
|
topic,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
|
|
@ -200,9 +202,7 @@ function RoomProfile({
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
alt={name}
|
alt={name}
|
||||||
renderFallback={() => (
|
renderFallback={() => <RoomIcon size="300" joinRule={joinRule} roomType={roomType} />}
|
||||||
<RoomIcon size="300" joinRule={joinRule ?? JoinRule.Restricted} filled />
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Box grow="Yes" direction="Column">
|
<Box grow="Yes" direction="Column">
|
||||||
|
|
@ -338,6 +338,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
||||||
{(localSummary) => (
|
{(localSummary) => (
|
||||||
<RoomProfile
|
<RoomProfile
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
|
roomType={localSummary.roomType}
|
||||||
name={localSummary.name}
|
name={localSummary.name}
|
||||||
topic={localSummary.topic}
|
topic={localSummary.topic}
|
||||||
avatarUrl={
|
avatarUrl={
|
||||||
|
|
@ -396,6 +397,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
||||||
{summary && (
|
{summary && (
|
||||||
<RoomProfile
|
<RoomProfile
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
|
roomType={summary.room_type}
|
||||||
name={summary.name || summary.canonical_alias || roomId}
|
name={summary.name || summary.canonical_alias || roomId}
|
||||||
topic={summary.topic}
|
topic={summary.topic}
|
||||||
avatarUrl={
|
avatarUrl={
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import { SearchOrderBy } from 'matrix-js-sdk';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { joinRuleToIconSrc } from '../../utils/room';
|
import { getRoomIconSrc } from '../../utils/room';
|
||||||
import { factoryRoomIdByAtoZ } from '../../utils/sort';
|
import { factoryRoomIdByAtoZ } from '../../utils/sort';
|
||||||
import {
|
import {
|
||||||
SearchItemStrGetter,
|
SearchItemStrGetter,
|
||||||
|
|
@ -274,9 +274,7 @@ function SelectRoomButton({ roomList, selectedRooms, onChange }: SelectRoomButto
|
||||||
before={
|
before={
|
||||||
<Icon
|
<Icon
|
||||||
size="50"
|
size="50"
|
||||||
src={
|
src={getRoomIconSrc(Icons, room.getType(), room.getJoinRule())}
|
||||||
joinRuleToIconSrc(Icons, room.getJoinRule(), false) ?? Icons.Hash
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -392,10 +390,7 @@ export function SearchFilters({
|
||||||
onClick={() => onSelectedRoomsChange(selectedRooms.filter((rId) => rId !== roomId))}
|
onClick={() => onSelectedRoomsChange(selectedRooms.filter((rId) => rId !== roomId))}
|
||||||
radii="Pill"
|
radii="Pill"
|
||||||
before={
|
before={
|
||||||
<Icon
|
<Icon size="50" src={getRoomIconSrc(Icons, room.getType(), room.getJoinRule())} />
|
||||||
size="50"
|
|
||||||
src={joinRuleToIconSrc(Icons, room.getJoinRule(), false) ?? Icons.Hash}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
after={<Icon size="50" src={Icons.Cross} />}
|
after={<Icon size="50" src={Icons.Cross} />}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { MouseEventHandler, forwardRef, useState, MouseEvent } from 'react';
|
import React, { MouseEventHandler, forwardRef, useState, MouseEvent } from 'react';
|
||||||
import { Room } from 'matrix-js-sdk';
|
import { EventType, Room } from 'matrix-js-sdk';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -21,7 +21,7 @@ import {
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { useFocusWithin, useHover } from 'react-aria';
|
import { useFocusWithin, useHover } from 'react-aria';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { NavButton, NavItem, NavItemContent, NavItemOptions } from '../../components/nav';
|
import { NavButton, NavItem, NavItemContent, NavItemOptions } from '../../components/nav';
|
||||||
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
|
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
|
||||||
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
|
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
|
||||||
|
|
@ -59,6 +59,7 @@ import { useCallMembers } from '../../hooks/useCallMemberships';
|
||||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||||
import { RoomNavUser } from './RoomNavUser';
|
import { RoomNavUser } from './RoomNavUser';
|
||||||
|
import { useRoomName } from '../../hooks/useRoomMeta';
|
||||||
|
|
||||||
type RoomNavItemMenuProps = {
|
type RoomNavItemMenuProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
|
@ -241,6 +242,10 @@ export function RoomNavItem({
|
||||||
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
||||||
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||||
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
||||||
|
const typingMember = useRoomTypingMember(room.roomId).filter(
|
||||||
|
(receipt) => receipt.userId !== mx.getUserId()
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isActiveCallReady,
|
isActiveCallReady,
|
||||||
activeCallRoomId,
|
activeCallRoomId,
|
||||||
|
|
@ -250,14 +255,20 @@ export function RoomNavItem({
|
||||||
toggleChat,
|
toggleChat,
|
||||||
hangUp,
|
hangUp,
|
||||||
} = useCallState();
|
} = useCallState();
|
||||||
const typingMember = useRoomTypingMember(room.roomId).filter(
|
|
||||||
(receipt) => receipt.userId !== mx.getUserId()
|
|
||||||
);
|
|
||||||
const isActiveCall = isActiveCallReady && activeCallRoomId === room.roomId;
|
const isActiveCall = isActiveCallReady && activeCallRoomId === room.roomId;
|
||||||
const callMemberships = useCallMembers(mx, room.roomId);
|
const callMemberships = useCallMembers(mx, room.roomId);
|
||||||
|
|
||||||
|
const powerLevels = usePowerLevels(room);
|
||||||
|
const creators = useRoomCreators(room);
|
||||||
|
const roomName = useRoomName(room);
|
||||||
|
|
||||||
|
const permissions = useRoomPermissions(creators, powerLevels);
|
||||||
|
const canJoinCall = permissions.event(EventType.GroupCallMemberPrefix, mx.getSafeUserId());
|
||||||
|
|
||||||
const { navigateRoom } = useRoomNavigate();
|
const { navigateRoom } = useRoomNavigate();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { roomIdOrAlias: viewedRoomId } = useParams();
|
|
||||||
const screenSize = useScreenSizeContext();
|
const screenSize = useScreenSizeContext();
|
||||||
const isMobile = screenSize === ScreenSize.Mobile;
|
const isMobile = screenSize === ScreenSize.Mobile;
|
||||||
|
|
||||||
|
|
@ -278,10 +289,7 @@ export function RoomNavItem({
|
||||||
const handleNavItemClick: MouseEventHandler<HTMLElement> = (evt) => {
|
const handleNavItemClick: MouseEventHandler<HTMLElement> = (evt) => {
|
||||||
if (room.isCallRoom()) {
|
if (room.isCallRoom()) {
|
||||||
if (!isMobile) {
|
if (!isMobile) {
|
||||||
if (!isActiveCall) {
|
if (!isActiveCall && canJoinCall) {
|
||||||
if (mx.getRoom(viewedRoomId)?.isCallRoom()) {
|
|
||||||
navigateRoom(room.roomId);
|
|
||||||
}
|
|
||||||
hangUp();
|
hangUp();
|
||||||
setActiveCallRoomId(room.roomId);
|
setActiveCallRoomId(room.roomId);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -307,7 +315,7 @@ export function RoomNavItem({
|
||||||
|
|
||||||
const optionsVisible = hover || !!menuAnchor;
|
const optionsVisible = hover || !!menuAnchor;
|
||||||
const ariaLabel = [
|
const ariaLabel = [
|
||||||
room.name,
|
roomName,
|
||||||
room.isCallRoom()
|
room.isCallRoom()
|
||||||
? [
|
? [
|
||||||
'Call Room',
|
'Call Room',
|
||||||
|
|
@ -345,10 +353,10 @@ export function RoomNavItem({
|
||||||
? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication)
|
? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||||
: getRoomAvatarUrl(mx, room, 96, useAuthentication)
|
: getRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||||
}
|
}
|
||||||
alt={room.name}
|
alt={roomName}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<Text as="span" size="H6">
|
<Text as="span" size="H6">
|
||||||
{nameInitials(room.name)}
|
{nameInitials(roomName)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -360,7 +368,8 @@ export function RoomNavItem({
|
||||||
filled={selected || isActiveCall}
|
filled={selected || isActiveCall}
|
||||||
size="100"
|
size="100"
|
||||||
joinRule={room.getJoinRule()}
|
joinRule={room.getJoinRule()}
|
||||||
call={room.isCallRoom()}
|
roomType={room.getType()}
|
||||||
|
locked={room.isCallRoom() && !canJoinCall}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
@ -371,7 +380,7 @@ export function RoomNavItem({
|
||||||
size="Inherit"
|
size="Inherit"
|
||||||
truncate
|
truncate
|
||||||
>
|
>
|
||||||
{room.name}
|
{roomName}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{!optionsVisible && !unread && !selected && typingMember.length > 0 && (
|
{!optionsVisible && !unread && !selected && typingMember.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -49,13 +49,6 @@ export const usePermissionGroups = (): PermissionGroup[] => {
|
||||||
const callSettingsGroup: PermissionGroup = {
|
const callSettingsGroup: PermissionGroup = {
|
||||||
name: 'Calls',
|
name: 'Calls',
|
||||||
items: [
|
items: [
|
||||||
{
|
|
||||||
location: {
|
|
||||||
state: true,
|
|
||||||
key: StateEvent.GroupCallPrefix,
|
|
||||||
},
|
|
||||||
name: 'Start Call',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
location: {
|
location: {
|
||||||
state: true,
|
state: true,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import {
|
||||||
Spinner,
|
Spinner,
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { JoinRule, Room } from 'matrix-js-sdk';
|
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';
|
||||||
|
|
@ -321,11 +321,7 @@ export function RoomViewHeader() {
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
alt={name}
|
alt={name}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<RoomIcon
|
<RoomIcon size="200" joinRule={room.getJoinRule()} roomType={room.getType()} />
|
||||||
size="200"
|
|
||||||
joinRule={room.getJoinRule() ?? JoinRule.Restricted}
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,7 @@ export function Search({ requestClose }: SearchProps) {
|
||||||
<RoomIcon
|
<RoomIcon
|
||||||
size="100"
|
size="100"
|
||||||
joinRule={room.getJoinRule()}
|
joinRule={room.getJoinRule()}
|
||||||
space={room.isSpaceRoom()}
|
roomType={room.getType()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps)
|
||||||
alt={roomName}
|
alt={roomName}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<RoomIcon
|
<RoomIcon
|
||||||
space
|
roomType={room.getType()}
|
||||||
size="50"
|
size="50"
|
||||||
joinRule={joinRuleContent?.join_rule ?? JoinRule.Invite}
|
joinRule={joinRuleContent?.join_rule ?? JoinRule.Invite}
|
||||||
filled
|
filled
|
||||||
|
|
|
||||||
|
|
@ -50,16 +50,8 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro
|
||||||
themeKind: ThemeKind | null
|
themeKind: ThemeKind | null
|
||||||
) => {
|
) => {
|
||||||
if (mx?.getUserId()) {
|
if (mx?.getUserId()) {
|
||||||
if (
|
if (activeCallRoomId && !isActiveCallReady) {
|
||||||
(activeCallRoomId !== viewedCallRoomId && isActiveCallReady) ||
|
const roomIdToSet = activeCallRoomId;
|
||||||
(activeCallRoomId && !isActiveCallReady) ||
|
|
||||||
(!activeCallRoomId && viewedCallRoomId && !isActiveCallReady)
|
|
||||||
) {
|
|
||||||
const roomIdToSet = (skipLobby ? activeCallRoomId : viewedCallRoomId) ?? '';
|
|
||||||
|
|
||||||
if (roomIdToSet === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const widgetId = `element-call-${roomIdToSet}-${Date.now()}`;
|
const widgetId = `element-call-${roomIdToSet}-${Date.now()}`;
|
||||||
const newUrl = getWidgetUrl(
|
const newUrl = getWidgetUrl(
|
||||||
|
|
@ -125,7 +117,6 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro
|
||||||
[
|
[
|
||||||
mx,
|
mx,
|
||||||
activeCallRoomId,
|
activeCallRoomId,
|
||||||
viewedCallRoomId,
|
|
||||||
isActiveCallReady,
|
isActiveCallReady,
|
||||||
clientConfig.elementCallUrl,
|
clientConfig.elementCallUrl,
|
||||||
activeClientWidget,
|
activeClientWidget,
|
||||||
|
|
|
||||||
|
|
@ -257,25 +257,60 @@ export const getUnreadInfos = (mx: MatrixClient): UnreadInfo[] => {
|
||||||
return unreadInfos;
|
return unreadInfos;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const joinRuleToIconSrc = (
|
export const getRoomIconSrc = (
|
||||||
icons: Record<IconName, IconSrc>,
|
icons: Record<IconName, IconSrc>,
|
||||||
joinRule: JoinRule,
|
roomType?: string,
|
||||||
space: boolean,
|
joinRule?: JoinRule,
|
||||||
call: boolean
|
locked?: boolean
|
||||||
): IconSrc | undefined => {
|
): IconSrc => {
|
||||||
if (joinRule === JoinRule.Restricted) {
|
type RoomIcons = {
|
||||||
return space ? icons.Space : call ? icons.VolumeHigh : icons.Hash;
|
base: IconSrc;
|
||||||
|
locked: IconSrc;
|
||||||
|
public: IconSrc;
|
||||||
|
};
|
||||||
|
|
||||||
|
const roomTypeIcons: Record<string, RoomIcons> = {
|
||||||
|
[RoomType.Call]: {
|
||||||
|
base: icons.VolumeHigh,
|
||||||
|
locked: icons.Lock,
|
||||||
|
public: icons.VolumeHigh,
|
||||||
|
},
|
||||||
|
[RoomType.Space]: {
|
||||||
|
base: icons.Space,
|
||||||
|
locked: icons.SpaceLock,
|
||||||
|
public: icons.SpaceGlobe,
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
base: icons.Hash,
|
||||||
|
locked: icons.HashLock,
|
||||||
|
public: icons.HashGlobe,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const roomIcons = roomTypeIcons[roomType ?? 'default'] ?? roomTypeIcons.default;
|
||||||
|
|
||||||
|
let roomIcon = roomIcons.base;
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
roomIcon = roomIcons.locked;
|
||||||
|
} else {
|
||||||
|
switch (joinRule) {
|
||||||
|
case JoinRule.Invite:
|
||||||
|
case JoinRule.Knock:
|
||||||
|
roomIcon = roomIcons.locked;
|
||||||
|
break;
|
||||||
|
case JoinRule.Restricted:
|
||||||
|
roomIcon = roomIcons.base;
|
||||||
|
break;
|
||||||
|
case JoinRule.Public:
|
||||||
|
roomIcon = roomIcons.public;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (joinRule === JoinRule.Knock) {
|
|
||||||
return space ? icons.SpaceLock : call ? icons.VolumeHigh : icons.HashLock;
|
return roomIcon;
|
||||||
}
|
|
||||||
if (joinRule === JoinRule.Invite) {
|
|
||||||
return space ? icons.SpaceLock : call ? icons.VolumeHigh : icons.HashLock;
|
|
||||||
}
|
|
||||||
if (joinRule === JoinRule.Public) {
|
|
||||||
return space ? icons.SpaceGlobe : call ? icons.VolumeHigh : icons.HashGlobe;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRoomAvatarUrl = (
|
export const getRoomAvatarUrl = (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue