diff --git a/src/app/components/create-room/CreateRoomKindSelector.tsx b/src/app/components/create-room/CreateRoomKindSelector.tsx index 096954fb..ebc55e7f 100644 --- a/src/app/components/create-room/CreateRoomKindSelector.tsx +++ b/src/app/components/create-room/CreateRoomKindSelector.tsx @@ -2,12 +2,8 @@ import React from 'react'; import { Box, Text, Icon, Icons, config, IconSrc } from 'folds'; import { SequenceCard } from '../sequence-card'; import { SettingTile } from '../setting-tile'; +import { CreateRoomKind } from './types'; -export enum CreateRoomKind { - Private = 'private', - Restricted = 'restricted', - Public = 'public', -} type CreateRoomKindSelectorProps = { value?: CreateRoomKind; onSelect: (value: CreateRoomKind) => void; diff --git a/src/app/components/create-room/CreateRoomVoiceSelector.tsx b/src/app/components/create-room/CreateRoomVoiceSelector.tsx new file mode 100644 index 00000000..f3862db2 --- /dev/null +++ b/src/app/components/create-room/CreateRoomVoiceSelector.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Box, Text, Icon, Icons, config, IconSrc } from 'folds'; +import { SequenceCard } from '../sequence-card'; +import { SettingTile } from '../setting-tile'; +import { CreateRoomVoice } from './types'; + +type CreateRoomVoiceSelectorProps = { + value?: CreateRoomVoice; + onSelect: (value: CreateRoomVoice) => void; + disabled?: boolean; + getIcon: (kind: CreateRoomVoice) => IconSrc; +}; +export function CreateRoomVoiceSelector({ + value, + onSelect, + disabled, + getIcon, +}: CreateRoomVoiceSelectorProps) { + return ( + + onSelect(CreateRoomVoice.TextRoom)} + disabled={disabled} + > + } + after={value === CreateRoomVoice.TextRoom && } + > + Text + + Send text messages, videos and GIFs. + + + + onSelect(CreateRoomVoice.VoiceRoom)} + disabled={disabled} + > + } + after={value === CreateRoomVoice.VoiceRoom && } + > + Voice + + A room optimized for voice calls. + + + + + ); +} diff --git a/src/app/components/create-room/index.ts b/src/app/components/create-room/index.ts index ffd9cb3d..a9c603b2 100644 --- a/src/app/components/create-room/index.ts +++ b/src/app/components/create-room/index.ts @@ -3,3 +3,4 @@ export * from './CreateRoomAliasInput'; export * from './RoomVersionSelector'; export * from './utils'; export * from './AdditionalCreatorInput'; +export * from './types'; diff --git a/src/app/components/create-room/types.ts b/src/app/components/create-room/types.ts new file mode 100644 index 00000000..e35af47a --- /dev/null +++ b/src/app/components/create-room/types.ts @@ -0,0 +1,10 @@ +export enum CreateRoomVoice { + TextRoom = 'text', + VoiceRoom = 'voice', +} + +export enum CreateRoomKind { + Private = 'private', + Restricted = 'restricted', + Public = 'public', +} diff --git a/src/app/components/create-room/utils.ts b/src/app/components/create-room/utils.ts index f10adf21..3d702f2a 100644 --- a/src/app/components/create-room/utils.ts +++ b/src/app/components/create-room/utils.ts @@ -7,11 +7,11 @@ import { Room, } from 'matrix-js-sdk'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; -import { CreateRoomKind } from './CreateRoomKindSelector'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { getViaServers } from '../../plugins/via-servers'; import { getMxIdServer } from '../../utils/matrix'; import { IPowerLevels } from '../../hooks/usePowerLevels'; +import { CreateRoomKind } from './types'; export const createRoomCreationContent = ( type: RoomType | undefined, diff --git a/src/app/features/call/SmallWidget.ts b/src/app/features/call/SmallWidget.ts index 0f9e4064..0f15b321 100644 --- a/src/app/features/call/SmallWidget.ts +++ b/src/app/features/call/SmallWidget.ts @@ -40,7 +40,7 @@ export const getWidgetUrl = ( roomId: string, elementCallUrl: string, widgetId: string, - setParams: any + setParams: any, ): URL => { const baseUrl = window.location.origin; const url = elementCallUrl @@ -54,6 +54,7 @@ export const getWidgetUrl = ( skipLobby: setParams.skipLobby ?? 'true', // TODO: skipLobby is deprecated, use intent instead (intent doesn't produce the same effect?) returnToLobby: setParams.returnToLobby ?? 'true', perParticipantE2EE: setParams.perParticipantE2EE ?? 'true', + callIntent: setParams.callIntent ?? 'video', header: 'none', confineToRoom: 'true', theme: setParams.theme ?? 'dark', @@ -123,7 +124,7 @@ export class SmallWidget extends EventEmitter { this.mockWidget, WidgetKind.Room, true, - this.roomId + this.roomId, ); this.iframe = iframe; this.messaging = new ClientWidgetApi(this.mockWidget, iframe, driver); @@ -189,7 +190,7 @@ export class SmallWidget extends EventEmitter { // MAKE PERSISTENT HERE // Send the ack after the widget actually has become sticky. } - } + }, ); return this.messaging; @@ -345,7 +346,7 @@ export const getWidgetData = ( client: MatrixClient, roomId: string, currentData: object, - overwriteData: object + overwriteData: object, ): IWidgetData => { // Example: Determine E2EE based on room state if needed const perParticipantE2EE = true; // Default or based on logic @@ -381,7 +382,7 @@ export const createVirtualWidget = ( url: URL, waitForIframeLoad: boolean, data: IWidgetData, - roomId: string + roomId: string, ): IApp => ({ client, id, diff --git a/src/app/features/create-room/CreateRoom.tsx b/src/app/features/create-room/CreateRoom.tsx index ef8e67be..9cc8bee8 100644 --- a/src/app/features/create-room/CreateRoom.tsx +++ b/src/app/features/create-room/CreateRoom.tsx @@ -1,5 +1,5 @@ import React, { FormEventHandler, useCallback, useEffect, useState } from 'react'; -import { MatrixError, Room } from 'matrix-js-sdk'; +import { MatrixError, Room, JoinRule } from 'matrix-js-sdk'; import { Box, Button, @@ -37,22 +37,40 @@ import { CreateRoomKindSelector, RoomVersionSelector, useAdditionalCreators, + CreateRoomVoice, } from '../../components/create-room'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { IPowerLevels } from '../../hooks/usePowerLevels'; +import { CreateRoomVoiceSelector } from '../../components/create-room/CreateRoomVoiceSelector'; +import { getRoomIconSrc } from '../../utils/room'; -const getCreateRoomKindToIcon = (kind: CreateRoomKind) => { - if (kind === CreateRoomKind.Private) return Icons.HashLock; - if (kind === CreateRoomKind.Restricted) return Icons.Hash; - return Icons.HashGlobe; +const getCreateRoomKindToIcon = (kind: CreateRoomKind, voice?: CreateRoomVoice) => { + const isVoiceRoom = voice === CreateRoomVoice.VoiceRoom; + + let joinRule: JoinRule = JoinRule.Public; + if (kind === CreateRoomKind.Restricted) joinRule = JoinRule.Restricted; + if (kind === CreateRoomKind.Private) joinRule = JoinRule.Knock; + + return getRoomIconSrc(Icons, isVoiceRoom ? RoomType.Call : undefined, joinRule); +}; + +const getCreateRoomVoiceToIcon = (kind: CreateRoomVoice) => { + if (kind === CreateRoomVoice.VoiceRoom) return Icons.VolumeHigh; + return Icons.Hash; }; type CreateRoomFormProps = { defaultKind?: CreateRoomKind; + defaultVoice?: CreateRoomVoice; space?: Room; onCreate?: (roomId: string) => void; }; -export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormProps) { +export function CreateRoomForm({ + defaultKind, + defaultVoice, + space, + onCreate, +}: CreateRoomFormProps) { const mx = useMatrixClient(); const alive = useAlive(); @@ -66,6 +84,7 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP const allowRestricted = space && restrictedSupported(selectedRoomVersion); + const [voice, setVoice] = useState(defaultVoice ?? CreateRoomVoice.TextRoom); const [kind, setKind] = useState( defaultKind ?? allowRestricted ? CreateRoomKind.Restricted : CreateRoomKind.Private ); @@ -74,7 +93,6 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP useAdditionalCreators(); const [federation, setFederation] = useState(true); const [encryption, setEncryption] = useState(false); - const [callRoom, setCallRoom] = useState(false); const [knock, setKnock] = useState(false); const [advance, setAdvance] = useState(false); @@ -123,7 +141,7 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP const powerOverrides: IPowerLevels = { events: {}, }; - if (callRoom) { + if (voice === CreateRoomVoice.VoiceRoom) { roomType = RoomType.Call; powerOverrides.events![StateEvent.GroupCallMemberPrefix] = 0; } @@ -150,6 +168,17 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP return ( + {!space && ( + + Type + + + )} Access getCreateRoomKindToIcon(roomKind, voice)} /> Name } + before={} name="nameInput" autoFocus size="500" @@ -184,20 +213,6 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP disabled={disabled} /> - - - } - /> - {kind === CreateRoomKind.Public && } diff --git a/src/app/features/create-room/CreateRoomModal.tsx b/src/app/features/create-room/CreateRoomModal.tsx index c9919ba9..5fa9b6ca 100644 --- a/src/app/features/create-room/CreateRoomModal.tsx +++ b/src/app/features/create-room/CreateRoomModal.tsx @@ -23,12 +23,13 @@ import { } from '../../state/hooks/createRoomModal'; import { CreateRoomModalState } from '../../state/createRoomModal'; import { stopPropagation } from '../../utils/keyboard'; +import { CreateRoomVoice } from '../../components/create-room/types'; type CreateRoomModalProps = { state: CreateRoomModalState; }; function CreateRoomModal({ state }: CreateRoomModalProps) { - const { spaceId } = state; + const { spaceId, voice } = state; const closeDialog = useCloseCreateRoomModal(); const allJoinedRooms = useAllJoinedRoomsSet(); @@ -57,7 +58,7 @@ function CreateRoomModal({ state }: CreateRoomModalProps) { }} > - New Room + New {voice === CreateRoomVoice.VoiceRoom && 'Voice '}Room @@ -74,7 +75,7 @@ function CreateRoomModal({ state }: CreateRoomModalProps) { direction="Column" gap="500" > - + diff --git a/src/app/features/lobby/SpaceItem.tsx b/src/app/features/lobby/SpaceItem.tsx index 64a97900..3dfbbdea 100644 --- a/src/app/features/lobby/SpaceItem.tsx +++ b/src/app/features/lobby/SpaceItem.tsx @@ -36,6 +36,7 @@ import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal'; import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal'; import { AddExistingModal } from '../add-existing'; +import { CreateRoomVoice } from '../../components/create-room/types'; function SpaceProfileLoading() { return ( @@ -249,8 +250,8 @@ function AddRoomButton({ item }: { item: HierarchyItem }) { setCords(evt.currentTarget.getBoundingClientRect()); }; - const handleCreateRoom = () => { - openCreateRoomModal(item.roomId); + const handleCreateRoom = (voice?: CreateRoomVoice) => { + openCreateRoomModal(item.roomId, voice); setCords(undefined); }; @@ -281,10 +282,19 @@ function AddRoomButton({ item }: { item: HierarchyItem }) { radii="300" variant="Primary" fill="None" - onClick={handleCreateRoom} + onClick={() => handleCreateRoom(CreateRoomVoice.TextRoom)} > New Room + handleCreateRoom(CreateRoomVoice.VoiceRoom)} + > + New Voice Room + Existing Room diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index 03c2d169..a8e35fbf 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -302,7 +302,7 @@ export function RoomNavItem({ navigateRoom(room.roomId); } } else { - navigateRoom(room.roomId); + navigate(linkPath); } }; diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index 5c430268..ac7a5aa3 100644 --- a/src/app/features/room/RoomViewHeader.tsx +++ b/src/app/features/room/RoomViewHeader.tsx @@ -268,7 +268,7 @@ export function RoomViewHeader() { const { isChatOpen, toggleChat } = useCallState(); const pinnedEvents = useRoomPinnedEvents(room); const encryptionEvent = useStateEvent(room, StateEvent.RoomEncryption); - const ecryptedRoom = !!encryptionEvent; + const encryptedRoom = !!encryptionEvent; const avatarMxc = useRoomAvatar(room, direct); const name = useRoomName(room); const topic = useRoomTopic(room); @@ -371,83 +371,83 @@ export function RoomViewHeader() { - {!ecryptedRoom && (!room.isCallRoom() || isChatOpen) && ( - - Search - - } - > - {(triggerRef) => ( - - - - )} - - )} {(!room.isCallRoom() || isChatOpen) && ( - - Pinned Messages - - } - > - {(triggerRef) => ( - + {!encryptedRoom && ( + + Search + + } > - {pinnedEvents.length > 0 && ( - - - {pinnedEvents.length} - - + {(triggerRef) => ( + + + )} - - + )} - - )} - {(!room.isCallRoom() || isChatOpen) && ( - setPinMenuAnchor(undefined), - clickOutsideDeactivates: true, - isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', - isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', - escapeDeactivates: stopPropagation, - }} - > - setPinMenuAnchor(undefined)} /> - - } - /> + + Pinned Messages + + } + > + {(triggerRef) => ( + + {pinnedEvents.length > 0 && ( + + + {pinnedEvents.length} + + + )} + + + )} + + setPinMenuAnchor(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', + isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, + }} + > + setPinMenuAnchor(undefined)} /> + + } + /> + )} {!room.isCallRoom() && screenSize === ScreenSize.Desktop && ( diff --git a/src/app/hooks/useRoomMeta.ts b/src/app/hooks/useRoomMeta.ts index 8b0ae8cc..086c3a56 100644 --- a/src/app/hooks/useRoomMeta.ts +++ b/src/app/hooks/useRoomMeta.ts @@ -20,6 +20,8 @@ export const useRoomName = (room: Room): string => { const [name, setName] = useState(room.name); useEffect(() => { + setName(room.name); + const handleRoomNameChange: RoomEventHandlerMap[RoomEvent.Name] = () => { setName(room.name); }; diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index 2e791da2..25744d27 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -68,9 +68,10 @@ import { Create } from './client/create'; import { CreateSpaceModalRenderer } from '../features/create-space'; import { SearchModalRenderer } from '../features/search'; import { getFallbackSession } from '../state/sessions'; +import { pushSessionToSW } from '../../sw-session'; import { CallProvider } from './client/call/CallProvider'; import { PersistentCallContainer } from './client/call/PersistentCallContainer'; -import { pushSessionToSW } from '../../sw-session'; + export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => { const { hashRouter } = clientConfig; diff --git a/src/app/pages/client/call/PersistentCallContainer.tsx b/src/app/pages/client/call/PersistentCallContainer.tsx index ea435a4c..3b5f8954 100644 --- a/src/app/pages/client/call/PersistentCallContainer.tsx +++ b/src/app/pages/client/call/PersistentCallContainer.tsx @@ -47,7 +47,7 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro smallWidgetRef: React.MutableRefObject, iframeRef: React.MutableRefObject, skipLobby: boolean, - themeKind: ThemeKind | null + themeKind: ThemeKind | null, ) => { if (mx?.getUserId()) { if (activeCallRoomId && !isActiveCallReady) { @@ -64,7 +64,8 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro returnToLobby: 'true', perParticipantE2EE: 'true', theme: themeKind, - } + callIntent: 'audio', + }, ); if ( @@ -96,8 +97,8 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro 'm.call', newUrl, true, - getWidgetData(mx, roomIdToSet, {}, { skipLobby: true }), - roomIdToSet + getWidgetData(mx, roomIdToSet, {}, { skipLobby: true, callIntent: 'audio' }), + roomIdToSet, ); const smallWidget = new SmallWidget(app); @@ -109,7 +110,7 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro roomIdToSet, widgetApiRef.current, smallWidget, - iframeElement + iframeElement, ); } } @@ -121,7 +122,7 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro clientConfig.elementCallUrl, activeClientWidget, registerActiveClientWidgetApi, - ] + ], ); useEffect(() => { diff --git a/src/app/state/createRoomModal.ts b/src/app/state/createRoomModal.ts index 81af5d5b..3d1a6803 100644 --- a/src/app/state/createRoomModal.ts +++ b/src/app/state/createRoomModal.ts @@ -1,7 +1,9 @@ import { atom } from 'jotai'; +import { CreateRoomVoice } from '../components/create-room/types'; export type CreateRoomModalState = { spaceId?: string; + voice?: CreateRoomVoice; }; export const createRoomModalAtom = atom(undefined); diff --git a/src/app/state/hooks/createRoomModal.ts b/src/app/state/hooks/createRoomModal.ts index 15db7289..dfb15017 100644 --- a/src/app/state/hooks/createRoomModal.ts +++ b/src/app/state/hooks/createRoomModal.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import { useAtomValue, useSetAtom } from 'jotai'; import { createRoomModalAtom, CreateRoomModalState } from '../createRoomModal'; +import { CreateRoomVoice } from '../../components/create-room/types'; export const useCreateRoomModalState = (): CreateRoomModalState | undefined => { const data = useAtomValue(createRoomModalAtom); @@ -19,13 +20,13 @@ export const useCloseCreateRoomModal = (): CloseCallback => { return close; }; -type OpenCallback = (space?: string) => void; +type OpenCallback = (space?: string, voice?: CreateRoomVoice) => void; export const useOpenCreateRoomModal = (): OpenCallback => { const setSettings = useSetAtom(createRoomModalAtom); const open: OpenCallback = useCallback( - (spaceId) => { - setSettings({ spaceId }); + (spaceId, voice) => { + setSettings({ spaceId, voice }); }, [setSettings] );