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:
James 2026-02-12 19:03:46 -05:00 committed by GitHub
parent 9554b31c7d
commit efb3e115db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 190 additions and 100 deletions

View file

@ -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;
}; };

View file

@ -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>
} }

View file

@ -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}
/>
)); ));

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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={

View file

@ -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} />}
> >

View file

@ -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 && (

View file

@ -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,

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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,

View file

@ -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 = (