From 12b58b242490b009dd223972acd6aaf5136d38ce Mon Sep 17 00:00:00 2001 From: Anton Alexeyev Date: Wed, 8 Oct 2025 11:54:56 +0700 Subject: [PATCH 01/11] Add pulse meeting presence Signed-off-by: Anton Alexeyev --- .../src/components/EditRoom.svelte | 8 +- .../src/components/ParticipantsList.svelte | 24 ++-- .../src/components/RoomButton.svelte | 26 ++-- .../src/components/RoomPopup.svelte | 4 +- .../src/components/WorkbenchExtension.svelte | 64 +++++++++- .../src/components/meeting/ControlExt.svelte | 112 ++++------------- .../invites/InviteEmployeeButton.svelte | 2 +- .../meeting/widget/MeetingWidget.svelte | 72 +++-------- plugins/love-resources/src/loveClient.ts | 2 +- plugins/love-resources/src/meetingPresence.ts | 118 ++++++++++++++++++ plugins/love-resources/src/meetings.ts | 39 ++++-- plugins/love-resources/src/stores.ts | 4 - plugins/love-resources/src/utils.ts | 26 ++-- 13 files changed, 295 insertions(+), 206 deletions(-) create mode 100644 plugins/love-resources/src/meetingPresence.ts diff --git a/plugins/love-resources/src/components/EditRoom.svelte b/plugins/love-resources/src/components/EditRoom.svelte index e079c679607..97249c65cfc 100644 --- a/plugins/love-resources/src/components/EditRoom.svelte +++ b/plugins/love-resources/src/components/EditRoom.svelte @@ -20,9 +20,9 @@ import love from '../plugin' import { getRoomName } from '../utils' - import { infos, myOffice, currentRoom } from '../stores' + import { infos, myOffice } from '../stores' import { lkSessionConnected } from '../liveKitClient' - import { createMeeting, joinMeeting } from '../meetings' + import { createMeeting, currentMeetingRoom, joinMeeting } from '../meetings' export let object: Room @@ -49,7 +49,7 @@ } } - $: connecting = tryConnecting || ($currentRoom?._id === object._id && !$lkSessionConnected) + $: connecting = tryConnecting || ($currentMeetingRoom?._id === object._id && !$lkSessionConnected) let connectLabel: IntlString = $infos.some(({ room }) => room === object._id) ? love.string.JoinMeeting @@ -95,7 +95,7 @@
- {#if showConnectionButton(object, connecting, $lkSessionConnected, $infos, $myOffice, $currentRoom)} + {#if showConnectionButton(object, connecting, $lkSessionConnected, $infos, $myOffice, $currentMeetingRoom)} {/if} diff --git a/plugins/love-resources/src/components/ParticipantsList.svelte b/plugins/love-resources/src/components/ParticipantsList.svelte index 8b987398ca4..27f677d922f 100644 --- a/plugins/love-resources/src/components/ParticipantsList.svelte +++ b/plugins/love-resources/src/components/ParticipantsList.svelte @@ -14,32 +14,30 @@ --> - {#each items as participant (participant.person)} + {#each items as item} + {@const person = $personByRefStore.get(item)} +
participant.onclick?.(e)} + class:cursor-pointer={item.onclick !== undefined} + on:click={(e) => item.onclick?.(e)} >
- +
- {formatName(participant.name)} + {formatName(person?.name ?? '')}
{/each}
diff --git a/plugins/love-resources/src/components/RoomButton.svelte b/plugins/love-resources/src/components/RoomButton.svelte index efeb6ec8c49..8430221286f 100644 --- a/plugins/love-resources/src/components/RoomButton.svelte +++ b/plugins/love-resources/src/components/RoomButton.svelte @@ -16,12 +16,12 @@ import { getEmbeddedLabel } from '@hcengineering/platform' import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources' import { tooltip, deviceOptionsStore as deviceInfo, checkAdaptiveMatching } from '@hcengineering/ui' - import { ParticipantInfo } from '@hcengineering/love' - import { formatName } from '@hcengineering/contact' + import { formatName, Person } from '@hcengineering/contact' import ParticipantsList from './ParticipantsList.svelte' + import { Ref } from '@hcengineering/core' export let label: string - export let participants: (ParticipantInfo & { onclick?: (e: MouseEvent) => void })[] + export let participants: { person: Ref, onclick?: (e: MouseEvent) => void }[] export let active: boolean = false export let limit: number = 4 @@ -41,16 +41,13 @@ > {label}
- {#each participants.slice(0, limit) as participant, i (participant._id)} + {#each participants.slice(0, limit) as participant, i (participant)} + {@const person = $personByRefStore.get(participant.person)}
- +
{/each}
@@ -61,16 +58,13 @@
{label}
- {#each participants as participant (participant._id)} + {#each participants as participant (participant)} + {@const person = $personByRefStore.get(participant.person)}
- +
{/each}
diff --git a/plugins/love-resources/src/components/RoomPopup.svelte b/plugins/love-resources/src/components/RoomPopup.svelte index df32a6e3d7b..07940e7e9f9 100644 --- a/plugins/love-resources/src/components/RoomPopup.svelte +++ b/plugins/love-resources/src/components/RoomPopup.svelte @@ -25,14 +25,14 @@ import { getObjectLinkFragment } from '@hcengineering/view-resources' import { createEventDispatcher } from 'svelte' import love from '../plugin' - import { currentMeetingMinutes, infos, myInfo } from '../stores' + import { infos, myInfo } from '../stores' import { lkSessionConnected } from '../liveKitClient' import MicrophoneButton from './meeting/controls/MicrophoneButton.svelte' import CameraButton from './meeting/controls/CameraButton.svelte' import ShareScreenButton from './meeting/controls/ShareScreenButton.svelte' import LeaveRoomButton from './meeting/controls/LeaveRoomButton.svelte' import MeetingHeader from './meeting/MeetingHeader.svelte' - import { joinMeeting } from '../meetings' + import { joinMeeting, currentMeetingMinutes } from '../meetings' export let room: Room diff --git a/plugins/love-resources/src/components/WorkbenchExtension.svelte b/plugins/love-resources/src/components/WorkbenchExtension.svelte index c95321ec25b..1d3cb2c3188 100644 --- a/plugins/love-resources/src/components/WorkbenchExtension.svelte +++ b/plugins/love-resources/src/components/WorkbenchExtension.svelte @@ -3,15 +3,29 @@ import { RemoteParticipant, RemoteTrack, RemoteTrackPublication, RoomEvent, Track } from 'livekit-client' import { onDestroy, onMount } from 'svelte' import love from '../plugin' - import { liveKitClient, lk } from '../utils' + import { createMeetingWidget, liveKitClient, lk, navigateToMeetingMinutes } from '../utils' import { lkSessionConnected } from '../liveKitClient' import { subscribeInviteRequests, unsubscribeInviteRequests } from '../invites' import { Room } from '@hcengineering/love' import { subscribeJoinRequests, unsubscribeJoinRequests } from '../joinRequests' import { Ref } from '@hcengineering/core' import { myInfo } from '../stores' + import { closeWidget, sidebarStore } from '@hcengineering/workbench-resources' + import { currentMeetingMinutes, currentMeetingRoom } from '../meetings' + import { getClient } from '@hcengineering/presentation' + import workbench from '@hcengineering/workbench' + import { + deleteMyMeetingPresence, + meetingPresenceTtlSeconds, + subscribeMeetingPresence, + unsubscribeMeetingPresence, + updateMyMeetingPresence + } from '../meetingPresence' let parentElement: HTMLDivElement + let presenceInterval: number | NodeJS.Timeout | undefined = undefined + let presenceRoom: Ref | undefined = undefined + const client = getClient() function handleTrackSubscribed ( track: RemoteTrack, @@ -54,16 +68,64 @@ lk.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed) await subscribeInviteRequests() + await subscribeMeetingPresence() }) onDestroy(async () => { await unsubscribeInviteRequests() + await unsubscribeMeetingPresence() lk.off(RoomEvent.TrackSubscribed, handleTrackSubscribed) lk.off(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed) if ($lkSessionConnected) { await liveKitClient.disconnect() } }) + + function checkActiveMeeting (meetingSessionConnected: boolean, room: Room | undefined): void { + const meetingWidgetState = $sidebarStore.widgetsState.get(love.ids.MeetingWidget) + const isMeetingWidgetCreated = meetingWidgetState !== undefined + + if (room === undefined) { + if (isMeetingWidgetCreated) { + closeWidget(love.ids.MeetingWidget) + void deletePresence() + } + return + } + + if (meetingSessionConnected) { + const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: love.ids.MeetingWidget })[0] + if (widget === undefined) return + + if (!isMeetingWidgetCreated) { + createMeetingWidget(widget, room._id, meetingSessionConnected) + presenceRoom = room._id + presenceInterval = setInterval(updatePresence, (meetingPresenceTtlSeconds - 2) * 1000) + void updatePresence() + void navigateToMeetingMinutes($currentMeetingMinutes) + } + } else { + if (isMeetingWidgetCreated) { + closeWidget(love.ids.MeetingWidget) + void deletePresence() + } + } + } + + async function deletePresence (): Promise { + if (presenceInterval === undefined || presenceRoom === undefined) return + clearInterval(presenceInterval) + presenceInterval = undefined + await deleteMyMeetingPresence(presenceRoom) + presenceRoom = undefined + } + + async function updatePresence (): Promise { + if (presenceRoom === undefined) return + await updateMyMeetingPresence(presenceRoom) + } + + $: checkActiveMeeting($lkSessionConnected, $currentMeetingRoom) diff --git a/plugins/love-resources/src/components/meeting/ControlExt.svelte b/plugins/love-resources/src/components/meeting/ControlExt.svelte index 7a990fc4d3a..ed54183be71 100644 --- a/plugins/love-resources/src/components/meeting/ControlExt.svelte +++ b/plugins/love-resources/src/components/meeting/ControlExt.svelte @@ -13,50 +13,22 @@ // limitations under the License. -->
- {#if activeRooms.length > 0} - - {#each activeRooms as active} - {#await getRoomName(active) then name} + {#if $activeRooms.length > 0} + {#each $activeRooms as activeRoom} + {#await getRoomName(activeRoom.room) then name} r._id === active._id) != null} - on:click={openRoom(active)} + active={activeRoom.myRoom} + on:click={openRoom(activeRoom.room)} + participants={activeRoom.persons.map((person) => ({ + person + }))} /> {/await} {/each} {/if} {#if reception !== undefined && receptionParticipants.length > 0} - {#if activeRooms.length > 0} + {#if $activeRooms.length > 0}
{/if} {#await getRoomName(reception) then name} ({ ...p, onclick: getParticipantClickHandler(p) }))} + participants={receptionParticipants.map((p) => ({ + person: p.person, + onclick: getParticipantClickHandler(p.person) + }))} /> {/await} {/if} diff --git a/plugins/love-resources/src/components/meeting/invites/InviteEmployeeButton.svelte b/plugins/love-resources/src/components/meeting/invites/InviteEmployeeButton.svelte index c9263b26523..f9de325d117 100644 --- a/plugins/love-resources/src/components/meeting/invites/InviteEmployeeButton.svelte +++ b/plugins/love-resources/src/components/meeting/invites/InviteEmployeeButton.svelte @@ -41,7 +41,7 @@ } function openSelectUsersPopup (): void { - const skipAccounts = $infos.filter((p) => p.room === currentMeetingRoom).map((p) => p.person) + const skipAccounts = $infos.filter((p) => p.room === $currentMeetingRoom?._id).map((p) => p.person) showPopup( SelectUsersPopup, { diff --git a/plugins/love-resources/src/components/meeting/widget/MeetingWidget.svelte b/plugins/love-resources/src/components/meeting/widget/MeetingWidget.svelte index 9be5eea2377..9f3642cc8d6 100644 --- a/plugins/love-resources/src/components/meeting/widget/MeetingWidget.svelte +++ b/plugins/love-resources/src/components/meeting/widget/MeetingWidget.svelte @@ -13,17 +13,12 @@ // limitations under the License. --> -{#if widgetState !== undefined && room} +{#if widgetState !== undefined && $currentMeetingRoom !== undefined}
- +
{#if widgetState.tab === 'video'} - + {:else if widgetState.tab === 'chat'} - {#if !isMeetingMinutesLoaded} + {#if $currentMeetingMinutes === undefined} - {:else if meetingMinutes} - + {:else} + {/if} {:else if widgetState.tab === 'transcription'} - {#if !isMeetingMinutesLoaded} + {#if $currentMeetingMinutes === undefined} - {:else if meetingMinutes} + {:else} - + {/if} diff --git a/plugins/love-resources/src/loveClient.ts b/plugins/love-resources/src/loveClient.ts index 0cbf95fa78b..6d94d038738 100644 --- a/plugins/love-resources/src/loveClient.ts +++ b/plugins/love-resources/src/loveClient.ts @@ -6,7 +6,7 @@ import { getPlatformToken, lk } from './utils' import { getCurrentEmployee } from '@hcengineering/contact' import { getPersonByPersonRef } from '@hcengineering/contact-resources' import { Analytics } from '@hcengineering/analytics' -import { currentMeetingMinutes } from './stores' +import { currentMeetingMinutes } from './meetings' import { get } from 'svelte/store' export function getLoveClient (): LoveClient { diff --git a/plugins/love-resources/src/meetingPresence.ts b/plugins/love-resources/src/meetingPresence.ts new file mode 100644 index 00000000000..4bbbb55b272 --- /dev/null +++ b/plugins/love-resources/src/meetingPresence.ts @@ -0,0 +1,118 @@ +import { getCurrentEmployee, type Person } from '@hcengineering/contact' +import { type Ref } from '@hcengineering/core' +import { type UnsubscribeCallback } from '@hcengineering/hulypulse-client' +import { type Room } from '@hcengineering/love' +import { getMetadata } from '@hcengineering/platform' +import presentation, { createPulseClient } from '@hcengineering/presentation' +import { get, writable } from 'svelte/store' +import { rooms } from './stores' + +const pulsePrefix = 'love/meeting' + +export interface MeetingPresence { + person: Ref + room: Ref +} + +export interface ActiveRoom { + room: Room + persons: Array> + myRoom: boolean +} + +export const activeRooms = writable([]) + +export const meetingPresenceTtlSeconds: number = 5 +let unsubscribePresenceCallback: UnsubscribeCallback | undefined +let personsByRoom: Map, Set>> | undefined +let presenceByKey: Map | undefined + +export async function subscribeMeetingPresence (): Promise { + const client = await createPulseClient() + if (client === undefined) return + + const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? '' + unsubscribePresenceCallback = await client.subscribe(`${workspace}/${pulsePrefix}/`, handleMeetingPresenceInfo) + personsByRoom = new Map, Set>>() + presenceByKey = new Map() +} + +export async function unsubscribeMeetingPresence (): Promise { + if (unsubscribePresenceCallback !== undefined) { + await unsubscribePresenceCallback() + unsubscribePresenceCallback = undefined + } + personsByRoom = undefined + presenceByKey = undefined +} + +function handleMeetingPresenceInfo (key: string, value: MeetingPresence | undefined): void { + if (personsByRoom === undefined || presenceByKey === undefined) return + let updateStore = false + if (value !== undefined) { + const roomSet = personsByRoom.get(value.room) + if (roomSet === undefined) { + personsByRoom.set(value.room, new Set>([value.person])) + updateStore = true + } else if (!roomSet.has(value.person)) { + roomSet.add(value.person) + updateStore = true + } + presenceByKey.set(key, value) + } else { + const presence = presenceByKey.get(key) + if (presence === undefined) return + presenceByKey.delete(key) + const roomSet = personsByRoom.get(presence.room) + if (roomSet === undefined) return + roomSet.delete(presence.person) + updateStore = true + if (roomSet.size === 0) { + personsByRoom.delete(presence.room) + updateStore = true + } + } + if (!updateStore) return + const person = getCurrentEmployee() + const newActiveRooms: ActiveRoom[] = [] + const roomsStore = get(rooms) + personsByRoom.forEach((participants: Set>, roomId: Ref) => { + const room = roomsStore.find((r) => r._id === roomId) + if (room === undefined) return + const persons = Array.from(participants) + newActiveRooms.push({ + room, + persons, + myRoom: participants.has(person) + }) + }) + activeRooms.set(newActiveRooms) +} + +export async function updateMyMeetingPresence (room: Ref): Promise { + const client = await createPulseClient() + const person = getCurrentEmployee() + + if (client !== undefined) { + const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? '' + try { + await client.put(`${workspace}/${pulsePrefix}/${room}/${person}`, { person, room }, meetingPresenceTtlSeconds) + } catch (error) { + console.warn('failed to put presence info:', error) + } + } +} + +export async function deleteMyMeetingPresence (room: Ref): Promise { + const client = await createPulseClient() + const person = getCurrentEmployee() + + if (client !== undefined) { + const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? '' + try { + await client.delete(`${workspace}/${pulsePrefix}/${room}/${person}`) + } catch (error) { + console.warn('failed to delete presence info:', error) + } + } +} diff --git a/plugins/love-resources/src/meetings.ts b/plugins/love-resources/src/meetings.ts index 4668ae8af41..cacd1b6225f 100644 --- a/plugins/love-resources/src/meetings.ts +++ b/plugins/love-resources/src/meetings.ts @@ -1,23 +1,33 @@ import core, { AccountRole, getCurrentAccount, type Ref } from '@hcengineering/core' -import love, { getFreeRoomPlace, MeetingStatus, type Room, RoomType, isOffice, RoomAccess } from '@hcengineering/love' -import presentation, { getClient } from '@hcengineering/presentation' +import love, { + getFreeRoomPlace, + MeetingStatus, + type Room, + RoomType, + isOffice, + RoomAccess, + type MeetingMinutes +} from '@hcengineering/love' +import presentation, { createQuery, getClient } from '@hcengineering/presentation' import { closeMeetingMinutes, getLiveKitEndpoint, getRoomName, liveKitClient, loveClient, - navigateToMeetingMinutes, navigateToOfficeDoc } from './utils' -import { get } from 'svelte/store' +import { get, writable } from 'svelte/store' import { infos, myInfo, myOffice, rooms } from './stores' import { getCurrentEmployee, type Person } from '@hcengineering/contact' import { getPersonByPersonRef } from '@hcengineering/contact-resources' import { getMetadata } from '@hcengineering/platform' import { sendJoinRequest, unsubscribeJoinRequests } from './joinRequests' -export let currentMeetingRoom: Ref | undefined +export const currentMeetingRoom = writable(undefined) +export const currentMeetingMinutes = writable(undefined) + +const meetingQuery = createQuery(true) export async function createMeeting (room: Room): Promise { if (room.access === RoomAccess.DND) return @@ -46,6 +56,7 @@ export async function createMeeting (room: Room): Promise { } export async function leaveMeeting (): Promise { + meetingQuery.unsubscribe() const client = getClient() const currentParticipationInfo = get(myInfo) const office = get(myOffice) @@ -70,7 +81,7 @@ export async function leaveMeeting (): Promise { await liveKitClient.disconnect() await unsubscribeJoinRequests() closeMeetingMinutes() - currentMeetingRoom = undefined + currentMeetingRoom.set(undefined) } export async function joinMeeting (room: Room): Promise { @@ -86,7 +97,7 @@ export async function joinMeeting (room: Room): Promise { } export async function joinOrCreateMeetingByInvite (roomId: Ref): Promise { - if (currentMeetingRoom === roomId) return + if (get(currentMeetingRoom)?._id === roomId) return const client = getClient() const room = getRoomById(roomId) @@ -116,22 +127,30 @@ export async function kick (person: Ref): Promise { async function connectToMeeting (room: Room): Promise { if (getCurrentAccount().role === AccountRole.ReadOnlyGuest) return - if (currentMeetingRoom === room._id) return + if (get(currentMeetingRoom)?._id === room._id) return if (currentMeetingRoom !== undefined) { await leaveMeeting() } - currentMeetingRoom = room._id + currentMeetingRoom.set(room) await navigateToOfficeDoc(room) await moveToMeetingRoom(room) try { + meetingQuery.query( + love.class.MeetingMinutes, + { attachedTo: room._id, status: MeetingStatus.Active }, + async (res) => { + const meetingMinutes = res[0] + currentMeetingMinutes.set(meetingMinutes) + meetingQuery.unsubscribe() + } + ) const token = await loveClient.getRoomToken(room) const wsURL = getLiveKitEndpoint() await liveKitClient.connect(wsURL, token, room.type === RoomType.Video) - await navigateToMeetingMinutes(room) } catch (err) { console.error(err) await leaveMeeting() diff --git a/plugins/love-resources/src/stores.ts b/plugins/love-resources/src/stores.ts index 670a2b98b15..8bda8d481b4 100644 --- a/plugins/love-resources/src/stores.ts +++ b/plugins/love-resources/src/stores.ts @@ -25,9 +25,6 @@ export const myInfo = derived(infos, (val) => { const personId = getCurrentEmployee() return val.find((p) => p.person === personId) }) -export const currentRoom = derived([rooms, myInfo], ([rooms, myInfo]) => { - return myInfo !== undefined ? rooms.find((p) => p._id === myInfo.room) : undefined -}) export const floors = writable([]) export const selectedFloor = writable | undefined>(undefined) export const activeFloor = derived([rooms, myInfo, myOffice], ([rooms, myInfo, myOffice]) => { @@ -44,7 +41,6 @@ export const activeFloor = derived([rooms, myInfo, myOffice], ([rooms, myInfo, m export const myPreferences = writable() export let $myPreferences: DevicesPreference | undefined -export const currentMeetingMinutes = writable(undefined) export const selectedRoomPlace = writable<{ _id: Ref, x: number, y: number } | undefined>(undefined) async function filterParticipantInfo (value: ParticipantInfo[]): Promise { diff --git a/plugins/love-resources/src/utils.ts b/plugins/love-resources/src/utils.ts index 1870183ecb9..e0b32d1262e 100644 --- a/plugins/love-resources/src/utils.ts +++ b/plugins/love-resources/src/utils.ts @@ -28,8 +28,7 @@ import { type MeetingSchedule, type Room, type RoomMetadata, - TranscriptionStatus, - MeetingStatus + TranscriptionStatus } from '@hcengineering/love' import { getEmbeddedLabel, getMetadata, getResource, type IntlString } from '@hcengineering/platform' import presentation, { @@ -60,10 +59,11 @@ import { getPersonByPersonRef } from '@hcengineering/contact-resources' import MeetingMinutesSearchItem from './components/MeetingMinutesSearchItem.svelte' import RoomSettingsPopup from './components/RoomSettingsPopup.svelte' import love from './plugin' -import { $myPreferences, currentMeetingMinutes, currentRoom } from './stores' +import { $myPreferences, rooms } from './stores' import { getLiveKitClient } from './liveKitClient' import { getLoveClient } from './loveClient' import { getClient as getAccountClientRaw } from '@hcengineering/account-client' +import { currentMeetingMinutes, currentMeetingRoom } from './meetings' export const liveKitClient = getLiveKitClient() export const lk: LKRoom = liveKitClient.liveKitRoom @@ -198,7 +198,7 @@ lk.on(RoomEvent.Connected, () => { }) async function initRoomMetadata (metadata: string | undefined): Promise { - const room = get(currentRoom) + const room = get(currentMeetingRoom) const data: RoomMetadata = parseMetadata(metadata) isTranscription.set(data.transcription === TranscriptionStatus.InProgress) @@ -267,16 +267,9 @@ export async function navigateToOfficeDoc (object: Doc): Promise { navigate(loc) } -export async function navigateToMeetingMinutes (room: Room): Promise { - const meeting = await getClient().findOne(love.class.MeetingMinutes, { - attachedTo: room._id, - status: MeetingStatus.Active - }) - if (meeting !== undefined) { - await navigateToOfficeDoc(meeting) - return - } - await navigateToOfficeDoc(room) +export async function navigateToMeetingMinutes (meetingMinutes: MeetingMinutes | undefined): Promise { + if (meetingMinutes === undefined) return + await navigateToOfficeDoc(meetingMinutes) } export function calculateFloorSize (_rooms: Room[], _preview?: boolean): number { @@ -379,14 +372,14 @@ export function getPlatformToken (): string { } export async function startTranscription (room: Room): Promise { - const current = get(currentRoom) + const current = get(currentMeetingRoom) if (current === undefined || room._id !== current._id) return await connectMeeting(room._id, room.language, { transcription: true }) } export async function stopTranscription (room: Room): Promise { - const current = get(currentRoom) + const current = get(currentMeetingRoom) if (current === undefined || room._id !== current._id) return await disconnectMeeting(room._id) @@ -431,6 +424,7 @@ export function isTranscriptionAllowed (): boolean { } export function createMeetingWidget (widget: Widget, room: Ref, video: boolean): void { + console.log('show widget') const tabs: WidgetTab[] = [ ...(video ? [ From f577a55214b7385add10cb28098d1cc5d08cb78e Mon Sep 17 00:00:00 2001 From: Anton Alexeyev Date: Wed, 8 Oct 2025 12:20:38 +0700 Subject: [PATCH 02/11] Fix room popup Signed-off-by: Anton Alexeyev --- .../src/components/RoomPopup.svelte | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/plugins/love-resources/src/components/RoomPopup.svelte b/plugins/love-resources/src/components/RoomPopup.svelte index 07940e7e9f9..aeef564d48f 100644 --- a/plugins/love-resources/src/components/RoomPopup.svelte +++ b/plugins/love-resources/src/components/RoomPopup.svelte @@ -19,13 +19,12 @@ import { IconArrowLeft, Location, ModernButton, Scroller, location, navigate, panelstore } from '@hcengineering/ui' - import { MeetingMinutes, ParticipantInfo, Room, loveId } from '@hcengineering/love' + import { MeetingMinutes, Room, loveId } from '@hcengineering/love' import { getClient } from '@hcengineering/presentation' import view from '@hcengineering/view' import { getObjectLinkFragment } from '@hcengineering/view-resources' import { createEventDispatcher } from 'svelte' import love from '../plugin' - import { infos, myInfo } from '../stores' import { lkSessionConnected } from '../liveKitClient' import MicrophoneButton from './meeting/controls/MicrophoneButton.svelte' import CameraButton from './meeting/controls/CameraButton.svelte' @@ -33,24 +32,13 @@ import LeaveRoomButton from './meeting/controls/LeaveRoomButton.svelte' import MeetingHeader from './meeting/MeetingHeader.svelte' import { joinMeeting, currentMeetingMinutes } from '../meetings' + import { activeRooms } from '../meetingPresence' export let room: Room + $: activeRoom = $activeRooms.find((r) => r.room._id === room._id) + $: myRoom = activeRoom?.myRoom === true const client = getClient() - async function getPerson (info: ParticipantInfo | undefined): Promise { - if (info === undefined) { - return null - } - - return await getPersonByPersonRef(info.person) - } - - let joined: boolean = false - $: joined = $myInfo?.room === room._id - - let info: ParticipantInfo[] = [] - $: info = $infos.filter((p) => p.room === room._id) - const dispatch = createEventDispatcher() async function connect (): Promise { @@ -91,8 +79,8 @@
- {#each info as inf} - {#await getPerson(inf) then person} + {#each activeRoom?.persons ?? [] as personRef} + {#await getPersonByPersonRef(personRef) then person} {#if person}
{/if} @@ -102,7 +90,7 @@
- {#if joined && $lkSessionConnected} + {#if myRoom && $lkSessionConnected}
@@ -110,7 +98,7 @@
{/if}
- {#if canGoBack(joined, $location, $currentMeetingMinutes)} + {#if canGoBack(myRoom, $location, $currentMeetingMinutes)} {/if} - {#if joined} + {#if myRoom} dispatch('close')} /> {:else} Date: Thu, 9 Oct 2025 11:23:30 +0700 Subject: [PATCH 03/11] Clear rooms store on destroy Signed-off-by: Anton Alexeyev --- plugins/love-resources/src/meetingPresence.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/love-resources/src/meetingPresence.ts b/plugins/love-resources/src/meetingPresence.ts index 4bbbb55b272..ed49c03172c 100644 --- a/plugins/love-resources/src/meetingPresence.ts +++ b/plugins/love-resources/src/meetingPresence.ts @@ -44,6 +44,7 @@ export async function unsubscribeMeetingPresence (): Promise { } personsByRoom = undefined presenceByKey = undefined + activeRooms.set([]) } function handleMeetingPresenceInfo (key: string, value: MeetingPresence | undefined): void { From 974745a34184151f2f84e2fb207707bf2d22ad8f Mon Sep 17 00:00:00 2001 From: Anton Alexeyev Date: Mon, 13 Oct 2025 19:30:26 +0700 Subject: [PATCH 04/11] Fix checks Signed-off-by: Anton Alexeyev --- .../src/components/MediaPopupItemExt.svelte | 10 ++-- .../love-resources/src/components/Room.svelte | 7 +-- .../src/components/RoomModal.svelte | 11 ++-- .../src/components/RoomPopup.svelte | 17 +++--- .../src/components/RoomPreview.svelte | 5 +- .../src/components/WorkbenchExtension.svelte | 10 ++-- .../src/components/meeting/ControlExt.svelte | 25 +++++---- .../components/meeting/MeetingHeader.svelte | 8 +-- .../meeting/ParticipantsListView.svelte | 35 ------------ .../meeting/controls/CameraButton.svelte | 4 +- .../meeting/controls/LeaveRoomButton.svelte | 4 +- .../meeting/widget/MeetingWidget.svelte | 2 +- .../meeting/widget/MeetingWidgetHeader.svelte | 19 ++++--- .../meeting/widget/WidgetSwitcher.svelte | 4 +- plugins/love-resources/src/meetingPresence.ts | 53 +++++++++---------- plugins/love-resources/src/utils.ts | 7 +++ 16 files changed, 95 insertions(+), 126 deletions(-) diff --git a/plugins/love-resources/src/components/MediaPopupItemExt.svelte b/plugins/love-resources/src/components/MediaPopupItemExt.svelte index a02a2119c66..f746c336063 100644 --- a/plugins/love-resources/src/components/MediaPopupItemExt.svelte +++ b/plugins/love-resources/src/components/MediaPopupItemExt.svelte @@ -4,11 +4,11 @@ import view from '@hcengineering/view' import { createEventDispatcher } from 'svelte' import love from '../plugin' - import { currentRoom, infos, myInfo, myOffice } from '../stores' + import { infos, myInfo, myOffice } from '../stores' import { liveKitClient } from '../utils' import { lkSessionConnected, ScreenSharingState, screenSharingState } from '../liveKitClient' import MeetingHeader from './meeting/MeetingHeader.svelte' - import { leaveMeeting } from '../meetings' + import { currentMeetingRoom, leaveMeeting } from '../meetings' export let limit: number = 4 @@ -35,16 +35,16 @@ await liveKitClient.setScreenShareEnabled(false) } - $: participants = $infos.filter((p) => p.room === $currentRoom?._id) + $: participants = $infos.filter((p) => p.room === $currentMeetingRoom?._id) $: personByRefStore = getPersonByPersonRefStore(participants.map((p) => p.person)) -{#if $lkSessionConnected && $currentRoom != null} +{#if $lkSessionConnected && $currentMeetingRoom != null} {@const overLimit = participants.length > limit}
- +
{#if overLimit} diff --git a/plugins/love-resources/src/components/Room.svelte b/plugins/love-resources/src/components/Room.svelte index ff7c0bff190..60cfd6273e3 100644 --- a/plugins/love-resources/src/components/Room.svelte +++ b/plugins/love-resources/src/components/Room.svelte @@ -20,11 +20,12 @@ import { onDestroy, onMount } from 'svelte' import love from '../plugin' - import { waitForOfficeLoaded, currentRoom } from '../stores' + import { waitForOfficeLoaded } from '../stores' import { isFullScreen, lk } from '../utils' import ControlBar from './meeting/ControlBar.svelte' import ParticipantsListView from './meeting/ParticipantsListView.svelte' import ScreenSharingView from './meeting/ScreenSharingView.svelte' + import { currentMeetingRoom } from '../meetings' export let canMaximize: boolean = true export let room: TypeRoom @@ -148,8 +149,8 @@ />
- {#if $currentRoom} - + {#if $currentMeetingRoom} + {/if}
diff --git a/plugins/love-resources/src/components/RoomModal.svelte b/plugins/love-resources/src/components/RoomModal.svelte index 5b2020e18c8..0a87369bcf0 100644 --- a/plugins/love-resources/src/components/RoomModal.svelte +++ b/plugins/love-resources/src/components/RoomModal.svelte @@ -16,19 +16,18 @@ import { createEventDispatcher } from 'svelte' import presentation from '@hcengineering/presentation' import { Modal } from '@hcengineering/ui' - import { RoomType } from '@hcengineering/love' - import { currentRoom } from '../stores' import RoomComponent from './Room.svelte' + import { currentMeetingRoom } from '../meetings' const dispatch = createEventDispatcher() - $: if ($currentRoom === undefined) { + $: if ($currentMeetingRoom === undefined) { dispatch('close') } -{#if $currentRoom !== undefined} +{#if $currentMeetingRoom !== undefined} dispatch('close')} > - {$currentRoom.name} + {$currentMeetingRoom.name} - + {/if} diff --git a/plugins/love-resources/src/components/RoomPopup.svelte b/plugins/love-resources/src/components/RoomPopup.svelte index aeef564d48f..02141b86cf8 100644 --- a/plugins/love-resources/src/components/RoomPopup.svelte +++ b/plugins/love-resources/src/components/RoomPopup.svelte @@ -13,13 +13,12 @@ // limitations under the License. -->
- {#if $activeRooms.length > 0} - {#each $activeRooms as activeRoom} - {#await getRoomName(activeRoom.room) then name} + {#if $ongoingMeetings.length > 0} + {#each $ongoingMeetings as ongoingMeeting} + {#await getMeetingName(ongoingMeeting) then name} ({ + active={$currentMeetingRoom?._id === ongoingMeeting.meetingId} + on:click={openMeeting(ongoingMeeting)} + participants={ongoingMeeting.persons.map((person) => ({ person }))} /> @@ -88,7 +87,7 @@ {/each} {/if} {#if reception !== undefined && receptionParticipants.length > 0} - {#if $activeRooms.length > 0} + {#if $ongoingMeetings.length > 0}
{/if} {#await getRoomName(reception) then name} diff --git a/plugins/love-resources/src/components/meeting/MeetingHeader.svelte b/plugins/love-resources/src/components/meeting/MeetingHeader.svelte index 9c25cbd0750..6647ea61c6b 100644 --- a/plugins/love-resources/src/components/meeting/MeetingHeader.svelte +++ b/plugins/love-resources/src/components/meeting/MeetingHeader.svelte @@ -5,7 +5,7 @@ import { getClient } from '@hcengineering/presentation' import { SortingOrder } from '@hcengineering/core' - export let room: Room + export let room: Room | undefined const client = getClient() let currentMeetingMinutes: MeetingMinutes | undefined @@ -13,7 +13,7 @@ $: void client .findOne( love.class.MeetingMinutes, - { attachedTo: room._id, status: MeetingStatus.Active }, + { attachedTo: room?._id, status: MeetingStatus.Active }, { sort: { createdOn: SortingOrder.Descending } } ) .then((res) => { @@ -47,7 +47,7 @@ {#if currentMeetingMinutes !== undefined}
- {room.name} + {room?.name} @@ -66,7 +66,7 @@ {:else} - {room.name} + {room?.name} {/if}
diff --git a/plugins/love-resources/src/components/meeting/ParticipantsListView.svelte b/plugins/love-resources/src/components/meeting/ParticipantsListView.svelte index a916c120feb..4161fbade92 100644 --- a/plugins/love-resources/src/components/meeting/ParticipantsListView.svelte +++ b/plugins/love-resources/src/components/meeting/ParticipantsListView.svelte @@ -1,16 +1,9 @@ diff --git a/plugins/love-resources/src/meetingPresence.ts b/plugins/love-resources/src/meetingPresence.ts index ed49c03172c..0065158fa45 100644 --- a/plugins/love-resources/src/meetingPresence.ts +++ b/plugins/love-resources/src/meetingPresence.ts @@ -4,27 +4,25 @@ import { type UnsubscribeCallback } from '@hcengineering/hulypulse-client' import { type Room } from '@hcengineering/love' import { getMetadata } from '@hcengineering/platform' import presentation, { createPulseClient } from '@hcengineering/presentation' -import { get, writable } from 'svelte/store' -import { rooms } from './stores' +import { writable } from 'svelte/store' const pulsePrefix = 'love/meeting' export interface MeetingPresence { person: Ref - room: Ref + meetingId: string } -export interface ActiveRoom { - room: Room +export interface OngoingMeeting { + meetingId: string persons: Array> - myRoom: boolean } -export const activeRooms = writable([]) +export const ongoingMeetings = writable([]) export const meetingPresenceTtlSeconds: number = 5 let unsubscribePresenceCallback: UnsubscribeCallback | undefined -let personsByRoom: Map, Set>> | undefined +let personsByMeeting: Map>> | undefined let presenceByKey: Map | undefined export async function subscribeMeetingPresence (): Promise { @@ -33,7 +31,7 @@ export async function subscribeMeetingPresence (): Promise { const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? '' unsubscribePresenceCallback = await client.subscribe(`${workspace}/${pulsePrefix}/`, handleMeetingPresenceInfo) - personsByRoom = new Map, Set>>() + personsByMeeting = new Map, Set>>() presenceByKey = new Map() } @@ -42,18 +40,18 @@ export async function unsubscribeMeetingPresence (): Promise { await unsubscribePresenceCallback() unsubscribePresenceCallback = undefined } - personsByRoom = undefined + personsByMeeting = undefined presenceByKey = undefined - activeRooms.set([]) + ongoingMeetings.set([]) } function handleMeetingPresenceInfo (key: string, value: MeetingPresence | undefined): void { - if (personsByRoom === undefined || presenceByKey === undefined) return + if (personsByMeeting === undefined || presenceByKey === undefined) return let updateStore = false if (value !== undefined) { - const roomSet = personsByRoom.get(value.room) + const roomSet = personsByMeeting.get(value.meetingId) if (roomSet === undefined) { - personsByRoom.set(value.room, new Set>([value.person])) + personsByMeeting.set(value.meetingId, new Set>([value.person])) updateStore = true } else if (!roomSet.has(value.person)) { roomSet.add(value.person) @@ -64,30 +62,25 @@ function handleMeetingPresenceInfo (key: string, value: MeetingPresence | undefi const presence = presenceByKey.get(key) if (presence === undefined) return presenceByKey.delete(key) - const roomSet = personsByRoom.get(presence.room) + const roomSet = personsByMeeting.get(presence.meetingId) if (roomSet === undefined) return roomSet.delete(presence.person) updateStore = true if (roomSet.size === 0) { - personsByRoom.delete(presence.room) + personsByMeeting.delete(presence.meetingId) updateStore = true } } if (!updateStore) return - const person = getCurrentEmployee() - const newActiveRooms: ActiveRoom[] = [] - const roomsStore = get(rooms) - personsByRoom.forEach((participants: Set>, roomId: Ref) => { - const room = roomsStore.find((r) => r._id === roomId) - if (room === undefined) return + const newMeetings: OngoingMeeting[] = [] + personsByMeeting.forEach((participants: Set>, meetingId: string) => { const persons = Array.from(participants) - newActiveRooms.push({ - room, - persons, - myRoom: participants.has(person) + newMeetings.push({ + meetingId, + persons }) }) - activeRooms.set(newActiveRooms) + ongoingMeetings.set(newMeetings) } export async function updateMyMeetingPresence (room: Ref): Promise { @@ -97,7 +90,11 @@ export async function updateMyMeetingPresence (room: Ref): Promise { if (client !== undefined) { const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? '' try { - await client.put(`${workspace}/${pulsePrefix}/${room}/${person}`, { person, room }, meetingPresenceTtlSeconds) + await client.put( + `${workspace}/${pulsePrefix}/${room}/${person}`, + { person, meetingId: room }, + meetingPresenceTtlSeconds + ) } catch (error) { console.warn('failed to put presence info:', error) } diff --git a/plugins/love-resources/src/utils.ts b/plugins/love-resources/src/utils.ts index e0b32d1262e..d93af127314 100644 --- a/plugins/love-resources/src/utils.ts +++ b/plugins/love-resources/src/utils.ts @@ -64,6 +64,7 @@ import { getLiveKitClient } from './liveKitClient' import { getLoveClient } from './loveClient' import { getClient as getAccountClientRaw } from '@hcengineering/account-client' import { currentMeetingMinutes, currentMeetingRoom } from './meetings' +import { type OngoingMeeting } from './meetingPresence' export const liveKitClient = getLiveKitClient() export const lk: LKRoom = liveKitClient.liveKitRoom @@ -239,6 +240,12 @@ export function closeMeetingMinutes (): void { currentMeetingMinutes.set(undefined) } +export async function getMeetingName (meeting: OngoingMeeting): Promise { + const room = get(rooms).find((r) => r._id === meeting.meetingId) + if (room === undefined) return '' + return await getRoomName(room) +} + export async function getRoomName (room: Room): Promise { if (isOffice(room) && room.person !== null && room.name === '') { const employee = await getPersonByPersonRef(room.person) From 30da627a9ad9ee90cb9f777504323e5500200c6e Mon Sep 17 00:00:00 2001 From: Anton Alexeyev Date: Mon, 13 Oct 2025 21:01:31 +0700 Subject: [PATCH 05/11] Pass meeting type into presence Signed-off-by: Anton Alexeyev --- .../src/components/RoomPopup.svelte | 12 +++--- .../src/components/WorkbenchExtension.svelte | 2 +- .../components/meeting/MeetingHeader.svelte | 17 +------- .../meeting/controls/LeaveRoomButton.svelte | 8 ++-- plugins/love-resources/src/meetingPresence.ts | 42 ++++++++++--------- plugins/love-resources/src/types.ts | 2 + 6 files changed, 37 insertions(+), 46 deletions(-) diff --git a/plugins/love-resources/src/components/RoomPopup.svelte b/plugins/love-resources/src/components/RoomPopup.svelte index 02141b86cf8..8bc1f4ba91d 100644 --- a/plugins/love-resources/src/components/RoomPopup.svelte +++ b/plugins/love-resources/src/components/RoomPopup.svelte @@ -31,12 +31,12 @@ import LeaveRoomButton from './meeting/controls/LeaveRoomButton.svelte' import MeetingHeader from './meeting/MeetingHeader.svelte' import { joinMeeting, currentMeetingMinutes, currentMeetingRoom } from '../meetings' - import { OngoingMeeting, ongoingMeetings } from '../meetingPresence' + import { OngoingMeeting } from '../meetingPresence' import { rooms } from '../stores' export let meeting: OngoingMeeting $: room = $rooms.find((r) => r._id === meeting.meetingId) - $: myRoom = $currentMeetingRoom?._id === meeting.meetingId + $: myMeeting = $currentMeetingRoom?._id === meeting.meetingId const client = getClient() const dispatch = createEventDispatcher() @@ -91,7 +91,7 @@
- {#if myRoom && $lkSessionConnected} + {#if myMeeting && $lkSessionConnected}
@@ -99,7 +99,7 @@
{/if}
- {#if canGoBack(myRoom, $location, $currentMeetingMinutes)} + {#if canGoBack(myMeeting, $location, $currentMeetingMinutes)} {/if} - {#if myRoom} - dispatch('close')} /> + {#if myMeeting} + dispatch('close')} /> {:else} { if (presenceRoom === undefined) return - await updateMyMeetingPresence(presenceRoom) + await updateMyMeetingPresence(presenceRoom, 'room') } $: checkActiveMeeting($lkSessionConnected, $currentMeetingRoom) diff --git a/plugins/love-resources/src/components/meeting/MeetingHeader.svelte b/plugins/love-resources/src/components/meeting/MeetingHeader.svelte index 6647ea61c6b..1046f5e1f87 100644 --- a/plugins/love-resources/src/components/meeting/MeetingHeader.svelte +++ b/plugins/love-resources/src/components/meeting/MeetingHeader.svelte @@ -46,8 +46,8 @@ {#if currentMeetingMinutes !== undefined}
- - {room?.name} + + {currentMeetingMinutes?.title} @@ -57,16 +57,3 @@ {/if}
{/if} - -
- - {#if currentMeetingMinutes !== undefined} - - {currentMeetingMinutes.title} - - {:else} - - {room?.name} - - {/if} -
diff --git a/plugins/love-resources/src/components/meeting/controls/LeaveRoomButton.svelte b/plugins/love-resources/src/components/meeting/controls/LeaveRoomButton.svelte index 73381f1197e..2aacdd17d06 100644 --- a/plugins/love-resources/src/components/meeting/controls/LeaveRoomButton.svelte +++ b/plugins/love-resources/src/components/meeting/controls/LeaveRoomButton.svelte @@ -2,17 +2,15 @@ import love from '../../../plugin' import { ModernButton } from '@hcengineering/ui' import { myInfo, myOffice } from '../../../stores' - import { isOffice, Room } from '@hcengineering/love' + import { isOffice } from '@hcengineering/love' import { createEventDispatcher } from 'svelte' - import { leaveMeeting } from '../../../meetings' + import { currentMeetingRoom, leaveMeeting } from '../../../meetings' - export let room: Room | undefined export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large' export let noLabel: boolean = false const dispatch = createEventDispatcher() - - $: isMyOffice = room !== undefined && isOffice(room) && $myInfo?.room === $myOffice?._id + $: isMyOffice = $currentMeetingRoom !== undefined && isOffice($currentMeetingRoom) && $myInfo?.room === $myOffice?._id async function leave (): Promise { await leaveMeeting() diff --git a/plugins/love-resources/src/meetingPresence.ts b/plugins/love-resources/src/meetingPresence.ts index 0065158fa45..b810e8e4a6e 100644 --- a/plugins/love-resources/src/meetingPresence.ts +++ b/plugins/love-resources/src/meetingPresence.ts @@ -5,16 +5,19 @@ import { type Room } from '@hcengineering/love' import { getMetadata } from '@hcengineering/platform' import presentation, { createPulseClient } from '@hcengineering/presentation' import { writable } from 'svelte/store' +import { type MeetingType } from './types' const pulsePrefix = 'love/meeting' export interface MeetingPresence { person: Ref meetingId: string + meetingType: MeetingType } export interface OngoingMeeting { meetingId: string + meetingType: MeetingType persons: Array> } @@ -22,7 +25,7 @@ export const ongoingMeetings = writable([]) export const meetingPresenceTtlSeconds: number = 5 let unsubscribePresenceCallback: UnsubscribeCallback | undefined -let personsByMeeting: Map>> | undefined +let meetingsById: Map> }> | undefined let presenceByKey: Map | undefined export async function subscribeMeetingPresence (): Promise { @@ -31,7 +34,7 @@ export async function subscribeMeetingPresence (): Promise { const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? '' unsubscribePresenceCallback = await client.subscribe(`${workspace}/${pulsePrefix}/`, handleMeetingPresenceInfo) - personsByMeeting = new Map, Set>>() + meetingsById = new Map> }>() presenceByKey = new Map() } @@ -40,21 +43,21 @@ export async function unsubscribeMeetingPresence (): Promise { await unsubscribePresenceCallback() unsubscribePresenceCallback = undefined } - personsByMeeting = undefined + meetingsById = undefined presenceByKey = undefined ongoingMeetings.set([]) } function handleMeetingPresenceInfo (key: string, value: MeetingPresence | undefined): void { - if (personsByMeeting === undefined || presenceByKey === undefined) return + if (meetingsById === undefined || presenceByKey === undefined) return let updateStore = false if (value !== undefined) { - const roomSet = personsByMeeting.get(value.meetingId) - if (roomSet === undefined) { - personsByMeeting.set(value.meetingId, new Set>([value.person])) + const meetingInfo = meetingsById.get(value.meetingId) + if (meetingInfo === undefined) { + meetingsById.set(value.meetingId, { type: value.meetingType, personsSet: new Set>([value.person]) }) updateStore = true - } else if (!roomSet.has(value.person)) { - roomSet.add(value.person) + } else if (!meetingInfo.personsSet.has(value.person)) { + meetingInfo.personsSet.add(value.person) updateStore = true } presenceByKey.set(key, value) @@ -62,28 +65,29 @@ function handleMeetingPresenceInfo (key: string, value: MeetingPresence | undefi const presence = presenceByKey.get(key) if (presence === undefined) return presenceByKey.delete(key) - const roomSet = personsByMeeting.get(presence.meetingId) - if (roomSet === undefined) return - roomSet.delete(presence.person) + const meetingInfo = meetingsById.get(presence.meetingId) + if (meetingInfo === undefined) return + meetingInfo.personsSet.delete(presence.person) updateStore = true - if (roomSet.size === 0) { - personsByMeeting.delete(presence.meetingId) + if (meetingInfo.personsSet.size === 0) { + meetingsById.delete(presence.meetingId) updateStore = true } } if (!updateStore) return const newMeetings: OngoingMeeting[] = [] - personsByMeeting.forEach((participants: Set>, meetingId: string) => { - const persons = Array.from(participants) + meetingsById.forEach((info: { type: MeetingType, personsSet: Set> }, meetingId: string) => { + const persons = Array.from(info.personsSet) newMeetings.push({ meetingId, + meetingType: info.type, persons }) }) ongoingMeetings.set(newMeetings) } -export async function updateMyMeetingPresence (room: Ref): Promise { +export async function updateMyMeetingPresence (meetingId: string, meetingType: MeetingType): Promise { const client = await createPulseClient() const person = getCurrentEmployee() @@ -91,8 +95,8 @@ export async function updateMyMeetingPresence (room: Ref): Promise { const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? '' try { await client.put( - `${workspace}/${pulsePrefix}/${room}/${person}`, - { person, meetingId: room }, + `${workspace}/${pulsePrefix}/${meetingId}/${person}`, + { person, meetingId, meetingType }, meetingPresenceTtlSeconds ) } catch (error) { diff --git a/plugins/love-resources/src/types.ts b/plugins/love-resources/src/types.ts index be32a6707d3..3418f3b208e 100644 --- a/plugins/love-resources/src/types.ts +++ b/plugins/love-resources/src/types.ts @@ -1,6 +1,8 @@ import { type DefSeparators } from '@hcengineering/ui' import { type RoomLanguage } from '@hcengineering/love' +export type MeetingType = 'room' | 'card' + export interface ResizeInitParams { x: number y: number From 8641ee901750f3e341cf5ee3a0794f992d91ffb7 Mon Sep 17 00:00:00 2001 From: Anton Alexeyev Date: Mon, 13 Oct 2025 23:31:43 +0700 Subject: [PATCH 06/11] Refactor current room handling Signed-off-by: Anton Alexeyev --- .../src/components/EditRoom.svelte | 12 ++--- .../src/components/MediaPopupItemExt.svelte | 43 ++---------------- .../love-resources/src/components/Room.svelte | 8 +--- .../src/components/RoomModal.svelte | 10 ++--- .../src/components/RoomPopup.svelte | 10 ++--- .../src/components/RoomPreview.svelte | 19 ++------ .../src/components/VideoPopup.svelte | 4 -- .../src/components/WorkbenchExtension.svelte | 30 ++++++------- .../src/components/meeting/ControlBar.svelte | 26 ++++------- .../src/components/meeting/ControlExt.svelte | 4 +- .../components/meeting/MeetingHeader.svelte | 29 +++--------- .../meeting/controls/CameraButton.svelte | 2 +- .../meeting/controls/LeaveRoomButton.svelte | 7 ++- .../controls/MeetingOptionsButton.svelte | 12 ++--- .../meeting/controls/RecordingButton.svelte | 6 +-- .../meeting/controls/RoomAccessButton.svelte | 37 ++++++++------- .../controls/TranscriptionButton.svelte | 6 +-- .../invites/InviteEmployeeButton.svelte | 4 +- .../meeting/widget/MeetingWidget.svelte | 9 ++-- .../meeting/widget/MeetingWidgetHeader.svelte | 12 ++--- .../meeting/widget/TranscriptionTab.svelte | 3 +- .../components/meeting/widget/VideoTab.svelte | 7 +-- .../meeting/widget/WidgetSwitcher.svelte | 2 +- plugins/love-resources/src/meetingPresence.ts | 5 +-- plugins/love-resources/src/meetings.ts | 17 +++---- plugins/love-resources/src/stores.ts | 9 +--- plugins/love-resources/src/utils.ts | 45 ++++++++++--------- 27 files changed, 144 insertions(+), 234 deletions(-) diff --git a/plugins/love-resources/src/components/EditRoom.svelte b/plugins/love-resources/src/components/EditRoom.svelte index 97249c65cfc..905d206757d 100644 --- a/plugins/love-resources/src/components/EditRoom.svelte +++ b/plugins/love-resources/src/components/EditRoom.svelte @@ -14,7 +14,7 @@ --> -{#if $lkSessionConnected && $currentMeetingRoom != null} - {@const overLimit = participants.length > limit} - +{#if $lkSessionConnected && $currentMeetingMinutes != null}
- +
- {#if overLimit} -
- {#each participants.slice(0, limit) as participant, i (participant._id)} -
- -
- {/each} -
- {:else} -
- {#each participants as participant (participant._id)} - - {/each} -
- {/if} -
{#if allowLeave || leaving} diff --git a/plugins/love-resources/src/components/Room.svelte b/plugins/love-resources/src/components/Room.svelte index 60cfd6273e3..3ff3f87f6b2 100644 --- a/plugins/love-resources/src/components/Room.svelte +++ b/plugins/love-resources/src/components/Room.svelte @@ -14,7 +14,6 @@ --> -{#if $currentMeetingRoom !== undefined} +{#if $currentMeetingMinutes !== undefined} dispatch('close')} > - {$currentMeetingRoom.name} + {$currentMeetingMinutes.title} - + {/if} diff --git a/plugins/love-resources/src/components/RoomPopup.svelte b/plugins/love-resources/src/components/RoomPopup.svelte index 8bc1f4ba91d..be96ad07f86 100644 --- a/plugins/love-resources/src/components/RoomPopup.svelte +++ b/plugins/love-resources/src/components/RoomPopup.svelte @@ -30,18 +30,18 @@ import ShareScreenButton from './meeting/controls/ShareScreenButton.svelte' import LeaveRoomButton from './meeting/controls/LeaveRoomButton.svelte' import MeetingHeader from './meeting/MeetingHeader.svelte' - import { joinMeeting, currentMeetingMinutes, currentMeetingRoom } from '../meetings' + import { joinMeeting, currentMeetingMinutes } from '../meetings' import { OngoingMeeting } from '../meetingPresence' - import { rooms } from '../stores' + import { getMeetingMinutesRoom } from '../utils' export let meeting: OngoingMeeting - $: room = $rooms.find((r) => r._id === meeting.meetingId) - $: myMeeting = $currentMeetingRoom?._id === meeting.meetingId + $: myMeeting = $currentMeetingMinutes?._id === meeting.meetingId const client = getClient() const dispatch = createEventDispatcher() async function connect (): Promise { + const room = await getMeetingMinutesRoom(meeting.meetingId as Ref) if (room === undefined) return await joinMeeting(room) dispatch('close') @@ -76,7 +76,7 @@
- +
diff --git a/plugins/love-resources/src/components/RoomPreview.svelte b/plugins/love-resources/src/components/RoomPreview.svelte index 54f558dd56d..36cd22059b4 100644 --- a/plugins/love-resources/src/components/RoomPreview.svelte +++ b/plugins/love-resources/src/components/RoomPreview.svelte @@ -15,7 +15,7 @@ diff --git a/plugins/love-resources/src/components/meeting/ControlBar.svelte b/plugins/love-resources/src/components/meeting/ControlBar.svelte index 76d2ba5fc97..dc91bf9d785 100644 --- a/plugins/love-resources/src/components/meeting/ControlBar.svelte +++ b/plugins/love-resources/src/components/meeting/ControlBar.svelte @@ -13,11 +13,9 @@ // limitations under the License. -->
- {#if room._id !== love.ids.Reception && $lkSessionConnected} - + {#if $lkSessionConnected} + - - + + {:else} - + {/if} @@ -102,10 +96,8 @@ on:click={maximize} /> {/if} - - {#if allowLeave} - - {/if} + + diff --git a/plugins/love-resources/src/components/meeting/ControlExt.svelte b/plugins/love-resources/src/components/meeting/ControlExt.svelte index 3f949ab76ce..817b61ffd19 100644 --- a/plugins/love-resources/src/components/meeting/ControlExt.svelte +++ b/plugins/love-resources/src/components/meeting/ControlExt.svelte @@ -24,7 +24,7 @@ import PersonActionPopup from '../PersonActionPopup.svelte' import RoomPopup from '../RoomPopup.svelte' import RoomButton from '../RoomButton.svelte' - import { currentMeetingRoom, leaveMeeting } from '../../meetings' + import { currentMeetingMinutes, leaveMeeting } from '../../meetings' import { lkSessionConnected } from '../../liveKitClient' import { OngoingMeeting, ongoingMeetings } from '../../meetingPresence' import { Person } from '@hcengineering/contact' @@ -77,7 +77,7 @@ {#await getMeetingName(ongoingMeeting) then name} ({ person diff --git a/plugins/love-resources/src/components/meeting/MeetingHeader.svelte b/plugins/love-resources/src/components/meeting/MeetingHeader.svelte index 1046f5e1f87..56b6c6fa6ac 100644 --- a/plugins/love-resources/src/components/meeting/MeetingHeader.svelte +++ b/plugins/love-resources/src/components/meeting/MeetingHeader.svelte @@ -1,24 +1,9 @@ -{#if currentMeetingMinutes !== undefined} +{#if meetingMinutes !== undefined}
- - {currentMeetingMinutes?.title} + + {meetingMinutes?.title} - {#if currentMeetingMinutes?.createdOn !== undefined} - {@const elapsed = now - currentMeetingMinutes.createdOn} + {#if meetingMinutes?.createdOn !== undefined} + {@const elapsed = now - meetingMinutes.createdOn}
{formatElapsedTime(elapsed)}
{/if}
diff --git a/plugins/love-resources/src/components/meeting/controls/CameraButton.svelte b/plugins/love-resources/src/components/meeting/controls/CameraButton.svelte index 67fbd3a7fe6..ff1557b5407 100644 --- a/plugins/love-resources/src/components/meeting/controls/CameraButton.svelte +++ b/plugins/love-resources/src/components/meeting/controls/CameraButton.svelte @@ -10,7 +10,7 @@ export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large' - $: allowCam = $currentMeetingRoom?.type === RoomType.Video + $: allowCam = $currentMeetingRoom?.type !== RoomType.Audio $: isCamEnabled = $state.camera?.enabled === true const client = getClient() diff --git a/plugins/love-resources/src/components/meeting/controls/LeaveRoomButton.svelte b/plugins/love-resources/src/components/meeting/controls/LeaveRoomButton.svelte index 2aacdd17d06..84e55f1589a 100644 --- a/plugins/love-resources/src/components/meeting/controls/LeaveRoomButton.svelte +++ b/plugins/love-resources/src/components/meeting/controls/LeaveRoomButton.svelte @@ -1,16 +1,15 @@ -{#if $lkSessionConnected && moreItems.length > 0} +{#if room !== undefined && $lkSessionConnected && moreItems.length > 0} import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core' import { ButtonBaseSize, ModernButton } from '@hcengineering/ui' - import { isRecording, isRecordingAvailable, loveClient } from '../../../utils' + import { isRecording, isRecordingAvailable, toggleRecording } from '../../../utils' import love from '../../../plugin' import { lkSessionConnected } from '../../../liveKitClient' - import { Room } from '@hcengineering/love' - export let room: Room export let size: ButtonBaseSize = 'large' export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary' @@ -18,6 +16,6 @@ disabled={!$lkSessionConnected} {kind} {size} - on:click={() => loveClient.record(room)} + on:click={() => toggleRecording()} /> {/if} diff --git a/plugins/love-resources/src/components/meeting/controls/RoomAccessButton.svelte b/plugins/love-resources/src/components/meeting/controls/RoomAccessButton.svelte index 23720213edc..681e189488a 100644 --- a/plugins/love-resources/src/components/meeting/controls/RoomAccessButton.svelte +++ b/plugins/love-resources/src/components/meeting/controls/RoomAccessButton.svelte @@ -5,31 +5,34 @@ import RoomAccessPopup from '../../RoomAccessPopup.svelte' import { getCurrentEmployee } from '@hcengineering/contact' - export let room: Room + export let room: Room | undefined export let size: ButtonBaseSize = 'large' export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary' const me = getCurrentEmployee() function setAccess (e: MouseEvent): void { + if (room === undefined) return if (isOffice(room) && room.person !== me) return showPopup(RoomAccessPopup, { room }, eventToHTMLElement(e)) } - +{#if room !== undefined} + +{/if} diff --git a/plugins/love-resources/src/components/meeting/controls/TranscriptionButton.svelte b/plugins/love-resources/src/components/meeting/controls/TranscriptionButton.svelte index 5c38fd840db..883b7e9a3cb 100644 --- a/plugins/love-resources/src/components/meeting/controls/TranscriptionButton.svelte +++ b/plugins/love-resources/src/components/meeting/controls/TranscriptionButton.svelte @@ -5,9 +5,7 @@ import love from '../../../plugin' import view from '@hcengineering/view' import { ButtonBaseSize, ModernButton } from '@hcengineering/ui' - import { Room } from '@hcengineering/love' - export let room: Room export let size: ButtonBaseSize = 'large' export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary' @@ -21,9 +19,9 @@ {size} on:click={() => { if ($isTranscription) { - void stopTranscription(room) + void stopTranscription() } else { - void startTranscription(room) + void startTranscription() } }} /> diff --git a/plugins/love-resources/src/components/meeting/invites/InviteEmployeeButton.svelte b/plugins/love-resources/src/components/meeting/invites/InviteEmployeeButton.svelte index f9de325d117..1068849719e 100644 --- a/plugins/love-resources/src/components/meeting/invites/InviteEmployeeButton.svelte +++ b/plugins/love-resources/src/components/meeting/invites/InviteEmployeeButton.svelte @@ -15,13 +15,13 @@ -{#if widgetState !== undefined && $currentMeetingRoom !== undefined} +{#if widgetState !== undefined && $currentMeetingMinutes !== undefined}
{#if widgetState.tab === 'video'} - + {:else if widgetState.tab === 'chat'} {#if $currentMeetingMinutes === undefined} @@ -65,7 +65,6 @@ {:else} - + {/if} diff --git a/plugins/love-resources/src/components/meeting/widget/MeetingWidgetHeader.svelte b/plugins/love-resources/src/components/meeting/widget/MeetingWidgetHeader.svelte index 4b105182d57..18adda75c09 100644 --- a/plugins/love-resources/src/components/meeting/widget/MeetingWidgetHeader.svelte +++ b/plugins/love-resources/src/components/meeting/widget/MeetingWidgetHeader.svelte @@ -30,7 +30,7 @@ import RecordingButton from '../controls/RecordingButton.svelte' import TranscriptionButton from '../controls/TranscriptionButton.svelte' import RoomAccessButton from '../controls/RoomAccessButton.svelte' - import { currentMeetingRoom } from '../../../meetings' + import { currentMeetingMinutes, currentMeetingRoom } from '../../../meetings' export let doc: MeetingMinutes | undefined = undefined @@ -40,12 +40,12 @@ $: breadcrumbs = [ { id: 'meeting', - title: doc?.title ?? $currentMeetingRoom?.name + title: doc?.title } ] function maximize (): void { - popup = showPopup(RoomModal, { room: $currentMeetingRoom }, 'full-centered') + popup = showPopup(RoomModal, {}, 'full-centered') } onDestroy(() => { @@ -56,10 +56,10 @@
- {#if $currentMeetingRoom !== undefined} + {#if $currentMeetingMinutes !== undefined} - - + + {/if} diff --git a/plugins/love-resources/src/components/meeting/widget/TranscriptionTab.svelte b/plugins/love-resources/src/components/meeting/widget/TranscriptionTab.svelte index cea431f7275..5f73ed1c94e 100644 --- a/plugins/love-resources/src/components/meeting/widget/TranscriptionTab.svelte +++ b/plugins/love-resources/src/components/meeting/widget/TranscriptionTab.svelte @@ -13,13 +13,12 @@ // limitations under the License. --> diff --git a/plugins/love-resources/src/components/meeting/widget/VideoTab.svelte b/plugins/love-resources/src/components/meeting/widget/VideoTab.svelte index c2a5b89e0b0..fe161d8124b 100644 --- a/plugins/love-resources/src/components/meeting/widget/VideoTab.svelte +++ b/plugins/love-resources/src/components/meeting/widget/VideoTab.svelte @@ -13,16 +13,11 @@ // limitations under the License. -->
- +