Uberf-13261: Improve office UX (#9733)

Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
Anton Alexeyev
2025-09-01 11:23:21 +07:00
committed by GitHub
parent c84bea5c96
commit 0b3e5aa8cf
34 changed files with 687 additions and 721 deletions
-4
View File
@@ -698,10 +698,6 @@ export function createModel (builder: Builder): void {
searchDisabled: true
})
builder.mixin(love.class.MeetingMinutes, core.class.Class, view.mixin.ObjectPanelFooter, {
editor: love.component.PanelControlBar
})
builder.createDoc(core.class.FullTextSearchContext, core.space.Model, {
toClass: love.class.MeetingMinutes,
fullTextSummary: true,
+1
View File
@@ -62,6 +62,7 @@
"@hcengineering/workbench": "^0.6.16",
"@hcengineering/workbench-resources": "^0.6.1",
"@hcengineering/emoji": "^0.6.0",
"@hcengineering/emoji-resources": "^0.6.0",
"@hcengineering/theme": "^0.6.5",
"@livekit/krisp-noise-filter": "^0.3.0",
"@livekit/track-processors": "^0.5.6",
@@ -1,326 +0,0 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { getCurrentEmployee } from '@hcengineering/contact'
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { Room, RoomType, isOffice, roomAccessIcon, RoomAccess } from '@hcengineering/love'
import { getResource } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import {
ButtonMenu,
DropdownIntlItem,
IconMaximize,
IconMoreV,
IconUpOutline,
ModernButton,
Popup,
SplitButton,
eventToHTMLElement,
showPopup,
TooltipInstance
} from '@hcengineering/ui'
import view, { Action } from '@hcengineering/view'
import { getActions } from '@hcengineering/view-resources'
import { toggleCamState, toggleMicState, state } from '@hcengineering/media-resources'
import love from '../plugin'
import { currentRoom, myInfo, myOffice } from '../stores'
import {
isFullScreen,
isRecording,
isRecordingAvailable,
isShareWithSound,
isSharingEnabled,
isTranscription,
isTranscriptionAllowed,
leaveRoom,
record,
screenSharing,
startTranscription,
stopTranscription,
lk,
liveKitClient
} from '../utils'
import CamSettingPopup from './meeting/CamSettingPopup.svelte'
import ControlBarContainer from './ControlBarContainer.svelte'
import MicSettingPopup from './meeting/MicSettingPopup.svelte'
import RoomAccessPopup from './RoomAccessPopup.svelte'
import RoomModal from './RoomModal.svelte'
import ShareSettingPopup from './ShareSettingPopup.svelte'
import { lkSessionConnected } from '../liveKitClient'
import emojiPlugin from '@hcengineering/emoji'
export let room: Room
export let canMaximize: boolean = true
export let fullScreen: boolean = false
export let onFullScreen: (() => void) | undefined = undefined
let allowCam: boolean = false
const allowShare: boolean = true
let allowLeave: boolean = false
let noLabel: boolean = false
$: allowCam = $currentRoom?.type === RoomType.Video
$: allowLeave = $myInfo?.room !== ($myOffice?._id ?? love.ids.Reception)
$: isMicEnabled = $state.microphone?.enabled === true
$: isCamEnabled = $state.camera?.enabled === true
async function changeShare (): Promise<void> {
const newValue = !$isSharingEnabled
const audio = newValue && $isShareWithSound
await liveKitClient.setScreenShareEnabled(newValue, audio)
}
async function leave (): Promise<void> {
await leaveRoom($myInfo, $myOffice)
}
function micSettings (e: MouseEvent): void {
showPopup(MicSettingPopup, {}, eventToHTMLElement(e))
}
function camSettings (e: MouseEvent): void {
showPopup(CamSettingPopup, {}, eventToHTMLElement(e))
}
function shareSettings (e: MouseEvent): void {
showPopup(ShareSettingPopup, {}, eventToHTMLElement(e))
}
function setAccess (e: MouseEvent): void {
if (isOffice(room) && room.person !== me) return
showPopup(RoomAccessPopup, { room }, eventToHTMLElement(e))
}
const me = getCurrentEmployee()
const client = getClient()
const camKeys = client.getModel().findAllSync(view.class.Action, { _id: love.action.ToggleVideo })?.[0]?.keyBinding
const micKeys = client.getModel().findAllSync(view.class.Action, { _id: love.action.ToggleMic })?.[0]?.keyBinding
let actions: Action[] = []
let moreItems: DropdownIntlItem[] = []
$: void getActions(client, room, love.class.Room).then((res) => {
actions = res
})
$: moreItems = actions.map((action) => ({
id: action._id,
label: action.label,
icon: action.icon
}))
async function handleMenuOption (e: CustomEvent<DropdownIntlItem['id']>): Promise<void> {
const action = actions.find((action) => action._id === e.detail)
if (action !== undefined) {
await handleAction(action)
}
}
async function handleAction (action: Action): Promise<void> {
const fn = await getResource(action.action)
await fn(room)
}
$: withVideo = $screenSharing || room.type === RoomType.Video
function maximize (): void {
showPopup(RoomModal, { room }, 'full-centered')
}
function addReaction (event: MouseEvent): void {
showPopup(
emojiPlugin.component.EmojiPopup,
{},
event?.target as HTMLElement,
async (result) => {
const emoji = result?.text
if (emoji == null) return
void lk.localParticipant.sendChatMessage(emoji, { topic: 'reaction' })
},
() => {}
)
}
</script>
<ControlBarContainer bind:noLabel>
<svelte:fragment slot="right">
{#if room._id !== love.ids.Reception && $lkSessionConnected}
<ModernButton
icon={roomAccessIcon[room.access]}
iconProps={{
fill:
room.access === RoomAccess.Open
? 'var(--bg-positive-default)'
: room.access === RoomAccess.DND
? 'var(--bg-negative-default)'
: 'currentColor'
}}
tooltip={{ label: love.string.ChangeAccess }}
kind={'secondary'}
size={'large'}
disabled={isOffice(room) && room.person !== me}
on:click={setAccess}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="center">
{#if $lkSessionConnected}
<ModernButton icon={emojiPlugin.icon.Emoji} kind={'secondary'} size={'large'} on:click={addReaction} />
<SplitButton
size={'large'}
icon={isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled}
showTooltip={{
label: isMicEnabled ? love.string.Mute : love.string.UnMute,
keys: micKeys
}}
action={toggleMicState}
secondIcon={IconUpOutline}
secondAction={micSettings}
separate
/>
{#if allowCam}
<SplitButton
size={'large'}
icon={isCamEnabled ? love.icon.CamEnabled : love.icon.CamDisabled}
showTooltip={{
label: isCamEnabled ? love.string.StopVideo : love.string.StartVideo,
keys: camKeys
}}
action={toggleCamState}
secondIcon={IconUpOutline}
secondAction={camSettings}
separate
/>
{/if}
{#if allowShare}
<SplitButton
size={'large'}
icon={$isSharingEnabled ? love.icon.SharingEnabled : love.icon.SharingDisabled}
iconProps={{
fill: $isSharingEnabled ? 'var(--bg-negative-default)' : 'var(--bg-positive-default)'
}}
showTooltip={{ label: $isSharingEnabled ? love.string.StopShare : love.string.Share }}
disabled={($screenSharing && !$isSharingEnabled) || !$lkSessionConnected}
action={changeShare}
secondIcon={IconUpOutline}
secondAction={shareSettings}
separate
/>
{/if}
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && $isRecordingAvailable}
<ModernButton
icon={$isRecording ? love.icon.StopRecord : love.icon.Record}
tooltip={{ label: $isRecording ? love.string.StopRecord : love.string.Record }}
disabled={!$lkSessionConnected}
kind={'secondary'}
size={'large'}
on:click={() => record(room)}
/>
{/if}
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && isTranscriptionAllowed() && $lkSessionConnected}
<ModernButton
icon={view.icon.Feather}
iconProps={$isTranscription ? { fill: 'var(--button-negative-BackgroundColor)' } : {}}
tooltip={{ label: $isTranscription ? love.string.StopTranscription : love.string.StartTranscription }}
kind="secondary"
size="large"
on:click={() => {
if ($isTranscription) {
void stopTranscription(room)
} else {
void startTranscription(room)
}
}}
/>
{/if}
{:else}
<ModernButton
icon={roomAccessIcon[room.access]}
iconProps={{
fill:
room.access === RoomAccess.Open
? 'var(--bg-positive-default)'
: room.access === RoomAccess.DND
? 'var(--bg-negative-default)'
: 'currentColor'
}}
tooltip={{ label: love.string.ChangeAccess }}
kind={'secondary'}
size={'large'}
disabled={isOffice(room) && room.person !== me}
on:click={setAccess}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="left">
{#if $lkSessionConnected && withVideo && onFullScreen}
<ModernButton
icon={$isFullScreen ? love.icon.ExitFullScreen : love.icon.FullScreen}
tooltip={{
label: $isFullScreen ? love.string.ExitingFullscreenMode : love.string.FullscreenMode,
direction: 'top'
}}
kind={'secondary'}
size={'large'}
on:click={() => {
$isFullScreen = !$isFullScreen
}}
/>
{/if}
{#if ($screenSharing || room.type === RoomType.Video) && $lkSessionConnected && canMaximize}
<ModernButton
icon={IconMaximize}
tooltip={{
label: love.string.FullscreenMode,
direction: 'top'
}}
kind={'secondary'}
iconSize="medium"
size={'large'}
on:click={maximize}
/>
{/if}
{#if $lkSessionConnected && moreItems.length > 0}
<ButtonMenu
items={moreItems}
icon={IconMoreV}
tooltip={{ label: love.string.MoreOptions, direction: 'top' }}
kind="secondary"
size="large"
noSelection
on:selected={handleMenuOption}
/>
{/if}
{#if allowLeave}
<ModernButton
icon={love.icon.LeaveRoom}
label={noLabel ? undefined : love.string.LeaveRoom}
tooltip={{ label: love.string.LeaveRoom, direction: 'top' }}
kind={'negative'}
size={'large'}
on:click={leave}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="extra">
{#if fullScreen}
<Popup fullScreen />
<TooltipInstance fullScreen />
{/if}
</svelte:fragment>
</ControlBarContainer>
@@ -21,8 +21,7 @@
import { Viewlet, ViewletPreference } from '@hcengineering/view'
import lovePlg from '../plugin'
import { currentRoom, floors } from '../stores'
import ControlBar from './ControlBar.svelte'
import { floors } from '../stores'
export let rooms: Room[] = []
export let floor: Ref<Floor>
@@ -63,7 +62,4 @@
<Component is={viewlet.$lookup.descriptor.component} props={{ floor, rooms }} on:open />
{/if}
</div>
{#if $currentRoom}
<ControlBar room={$currentRoom} />
{/if}
</div>
@@ -16,12 +16,11 @@
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { onDestroy, onMount } from 'svelte'
import presentation from '@hcengineering/presentation'
import { RoomType } from '@hcengineering/love'
import Hall from './Hall.svelte'
import { getMetadata } from '@hcengineering/platform'
import love from '../plugin'
import { tryConnect, screenSharing } from '../utils'
import { tryConnect } from '../utils'
import { infos, invites, myInfo, myRequests, waitForOfficeLoaded, currentRoom } from '../stores'
import { lkSessionConnected } from '../liveKitClient'
@@ -48,8 +47,7 @@
if (
!$lkSessionConnected &&
(room.type === RoomType.Video || $screenSharing) &&
$myInfo?.sessionId &&
$myInfo?.sessionId !== undefined &&
$myInfo.sessionId === getMetadata(presentation.metadata.SessionId)
) {
const info = $infos.filter((p) => p.room === room._id)
@@ -3,12 +3,12 @@
import { isOffice } from '@hcengineering/love'
import { Button } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { DocNavLink } from '@hcengineering/view-resources'
import { createEventDispatcher, onMount } from 'svelte'
import { createEventDispatcher } from 'svelte'
import love from '../plugin'
import { currentRoom, infos, myInfo, myOffice, rooms, currentMeetingMinutes } from '../stores'
import { endMeeting, leaveRoom } from '../utils'
import { currentRoom, infos, myInfo, myOffice, rooms } from '../stores'
import { endMeeting, isSharingEnabled, leaveRoom, liveKitClient } from '../utils'
import { lkSessionConnected } from '../liveKitClient'
import MeetingHeader from './meeting/MeetingHeader.svelte'
export let limit: number = 4
@@ -34,31 +34,12 @@
dispatch('close')
}
function formatElapsedTime (elapsed: number): string {
const seconds = Math.floor(elapsed / 1000)
const minutes = Math.floor(seconds / 60)
const hours = Math.floor(minutes / 60)
const displaySeconds = (seconds % 60).toString().padStart(2, '0')
const displayMinutes = (minutes % 60).toString().padStart(2, '0')
return hours > 0 ? `${hours}:${displayMinutes}:${displaySeconds}` : `${displayMinutes}:${displaySeconds}`
async function stopShare (): Promise<void> {
await liveKitClient.setScreenShareEnabled(false)
}
let now = Date.now()
$: participants = $infos.filter((p) => p.room === $currentRoom?._id)
$: personByRefStore = getPersonByPersonRefStore(participants.map((p) => p.person))
onMount(() => {
const interval = setInterval(() => {
now = Date.now()
}, 1000)
return () => {
clearInterval(interval)
}
})
</script>
{#if $lkSessionConnected && $currentRoom != null}
@@ -66,34 +47,7 @@
<div class="m-1 p-2">
<div class="flex-col flex-grow flex-gap-0-5">
<!-- subtitle -->
{#if $currentMeetingMinutes !== undefined}
<div class="flex-between flex-gap-2">
<DocNavLink object={$currentRoom}>
<span class="font-medium-12 secondary-textColor overflow-label">{$currentRoom.name}</span>
</DocNavLink>
<!-- elapsed time from start -->
{#if $currentMeetingMinutes?.createdOn !== undefined}
{@const elapsed = now - $currentMeetingMinutes.createdOn}
<div class="font-medium-12 secondary-textColor">{formatElapsedTime(elapsed)}</div>
{/if}
</div>
{/if}
<div class="flex-between flex-gap-2">
<!-- title -->
{#if $currentMeetingMinutes !== undefined}
<DocNavLink object={$currentMeetingMinutes}>
<span class="font-medium overflow-label">{$currentMeetingMinutes.title}</span>
</DocNavLink>
{:else}
<DocNavLink object={$currentRoom}>
<span class="font-medium overflow-label">{$currentRoom.name}</span>
</DocNavLink>
{/if}
</div>
<MeetingHeader room={$currentRoom} />
<div class="flex-between items-start flex-gap-2 mt-2">
<!-- Avatars -->
{#if overLimit}
@@ -123,28 +77,41 @@
</div>
{/if}
<!-- Leave Button -->
{#if allowLeave || leaving}
<Button
icon={love.icon.LeaveRoom}
kind={'dangerous'}
size={'x-small'}
label={view.string.Leave}
showTooltip={{ label: love.string.LeaveRoom }}
padding={'0 .5rem'}
on:click={handleLeaveClick}
/>
{:else if isMyOffice || ending}
<Button
icon={love.icon.LeaveRoom}
kind={'dangerous'}
size={'x-small'}
label={love.string.EndMeeting}
showTooltip={{ label: love.string.EndMeeting }}
padding={'0 .5rem'}
on:click={handleEndMeetingClick}
/>
{/if}
<div class="flex-row-center flex-gap-3">
<!-- Leave Button -->
{#if allowLeave || leaving}
<Button
icon={love.icon.LeaveRoom}
kind={$isSharingEnabled ? 'regular' : 'dangerous'}
size={'x-small'}
label={view.string.Leave}
showTooltip={{ label: love.string.LeaveRoom }}
padding={'0 .5rem'}
on:click={handleLeaveClick}
/>
{:else if isMyOffice || ending}
<Button
icon={love.icon.LeaveRoom}
kind={$isSharingEnabled ? 'regular' : 'dangerous'}
size={'x-small'}
label={love.string.EndMeeting}
showTooltip={{ label: love.string.EndMeeting }}
padding={'0 .5rem'}
on:click={handleEndMeetingClick}
/>
{/if}
{#if $isSharingEnabled}
<Button
icon={love.icon.SharingEnabled}
kind="dangerous"
size={'x-small'}
label={love.string.StopShare}
showTooltip={{ label: love.string.StopShare }}
padding={'0 .5rem'}
on:click={stopShare}
/>
{/if}
</div>
</div>
</div>
</div>
@@ -1,29 +0,0 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { MeetingMinutes } from '@hcengineering/love'
import ControlBar from './ControlBar.svelte'
import { currentRoom, currentMeetingMinutes } from '../stores'
export let object: MeetingMinutes
</script>
{#if $currentRoom && $currentMeetingMinutes?._id === object._id}
<div class="flex-grow flex-shrink">
<ControlBar room={$currentRoom} />
</div>
{/if}
@@ -22,7 +22,7 @@
import love from '../plugin'
import { waitForOfficeLoaded, currentRoom, infos, invites, myInfo, myRequests } from '../stores'
import { isFullScreen, lk, tryConnect } from '../utils'
import ControlBar from './ControlBar.svelte'
import ControlBar from './meeting/ControlBar.svelte'
import { lkSessionConnected } from '../liveKitClient'
import ParticipantsListView from './meeting/ParticipantsListView.svelte'
import ScreenSharingView from './meeting/ScreenSharingView.svelte'
@@ -16,28 +16,29 @@
import { createEventDispatcher } from 'svelte'
import presentation from '@hcengineering/presentation'
import { Modal } from '@hcengineering/ui'
import love, { RoomType } from '@hcengineering/love'
import { RoomType } from '@hcengineering/love'
import { currentRoom } from '../stores'
import RoomComponent from './Room.svelte'
import { screenSharing } from '../utils'
const dispatch = createEventDispatcher()
$: if ($currentRoom === undefined || (!$screenSharing && $currentRoom.type !== RoomType.Video)) {
$: if ($currentRoom === undefined) {
dispatch('close')
}
</script>
{#if ($currentRoom !== undefined && $screenSharing) || $currentRoom?.type === RoomType.Video}
{#if $currentRoom !== undefined}
<Modal
label={love.string.Room}
type="type-popup"
okLabel={presentation.string.Create}
hideFooter
padding="0"
on:close={() => dispatch('close')}
>
<svelte:fragment slot="title">
{$currentRoom.name}
</svelte:fragment>
<RoomComponent withVideo={$currentRoom.type === RoomType.Video} room={$currentRoom} canMaximize={false} />
</Modal>
{/if}
@@ -13,57 +13,26 @@
// limitations under the License.
-->
<script lang="ts">
import { Person, getCurrentEmployee } from '@hcengineering/contact'
import { Person } from '@hcengineering/contact'
import { UserInfo, getPersonByPersonRef } from '@hcengineering/contact-resources'
import { Class, Doc, Ref } from '@hcengineering/core'
import {
IconArrowLeft,
IconUpOutline,
Label,
Location,
ModernButton,
Scroller,
SplitButton,
eventToHTMLElement,
location,
navigate,
panelstore,
showPopup
} from '@hcengineering/ui'
import { IconArrowLeft, Location, ModernButton, Scroller, location, navigate, panelstore } from '@hcengineering/ui'
import {
MeetingMinutes,
ParticipantInfo,
Room,
RoomType,
isOffice,
loveId,
roomAccessIcon,
roomAccessLabel
} from '@hcengineering/love'
import { MeetingMinutes, ParticipantInfo, Room, loveId } from '@hcengineering/love'
import { getClient } from '@hcengineering/presentation'
import view from '@hcengineering/view'
import { getObjectLinkFragment } from '@hcengineering/view-resources'
import { toggleCamState, toggleMicState, state } from '@hcengineering/media-resources'
import { createEventDispatcher } from 'svelte'
import love from '../plugin'
import { currentMeetingMinutes, currentRoom, infos, invites, myInfo, myOffice, myRequests, rooms } from '../stores'
import {
endMeeting,
getRoomName,
isShareWithSound,
isSharingEnabled,
leaveRoom,
liveKitClient,
screenSharing,
tryConnect
} from '../utils'
import { currentMeetingMinutes, infos, invites, myInfo, myRequests } from '../stores'
import { tryConnect } from '../utils'
import { lkSessionConnected } from '../liveKitClient'
import CamSettingPopup from './meeting/CamSettingPopup.svelte'
import MicSettingPopup from './meeting/MicSettingPopup.svelte'
import RoomAccessPopup from './RoomAccessPopup.svelte'
import ShareSettingPopup from './ShareSettingPopup.svelte'
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'
export let room: Room
@@ -79,45 +48,11 @@
let joined: boolean = false
$: joined = $myInfo?.room === room._id
$: isMyOffice = $myInfo?.room === $myOffice?._id
$: allowLeave = !isMyOffice && $myInfo?.room !== love.ids.Reception
let info: ParticipantInfo[] = []
$: info = $infos.filter((p) => p.room === room._id)
let allowCam: boolean = false
const allowShare: boolean = true
$: allowCam = $currentRoom?.type === RoomType.Video
$: isMicEnabled = $state.microphone?.enabled === true
$: isCamEnabled = $state.camera?.enabled === true
const dispatch = createEventDispatcher()
async function changeShare (): Promise<void> {
const newValue = !$isSharingEnabled
const audio = newValue && $isShareWithSound
await liveKitClient.setScreenShareEnabled(newValue, audio)
dispatch('close')
}
async function leave (): Promise<void> {
await leaveRoom($myInfo, $myOffice)
dispatch('close')
}
async function end (): Promise<void> {
if (isOffice(room) && $myInfo !== undefined) {
await endMeeting(room, $rooms, $infos, $myInfo)
}
dispatch('close')
}
function shareSettings (e: MouseEvent): void {
showPopup(ShareSettingPopup, {}, eventToHTMLElement(e))
}
async function connect (): Promise<void> {
await tryConnect($myInfo, room, info, $myRequests, $invites)
dispatch('close')
@@ -139,23 +74,6 @@
}
}
function micSettings (e: MouseEvent): void {
showPopup(MicSettingPopup, {}, eventToHTMLElement(e))
}
function camSettings (e: MouseEvent): void {
showPopup(CamSettingPopup, {}, eventToHTMLElement(e))
}
function setAccess (e: MouseEvent): void {
showPopup(RoomAccessPopup, { room }, eventToHTMLElement(e), (res) => {
if (res !== undefined) {
room.access = res
}
})
}
const me = getCurrentEmployee()
function canGoBack (joined: boolean, location: Location, meetingMinutes?: MeetingMinutes): boolean {
if (!joined) return false
if (location.path[2] !== loveId) return true
@@ -168,13 +86,8 @@
}
</script>
<div class="antiPopup room-popup">
<div class="room-label"><Label label={love.string.Room} /></div>
<div class="title overflow-label">
{#await getRoomName(room) then name}
{name}
{/await}
</div>
<div class="antiPopup room-popup flex-gap-4">
<MeetingHeader {room} />
<div class="room-popup__content">
<Scroller padding={'0.5rem'} stickedScrollBars>
<div class="room-popup__content-grid">
@@ -188,98 +101,37 @@
</div>
</Scroller>
</div>
{#if joined && $lkSessionConnected}
<div class="room-btns" class:no-video={!allowCam}>
<SplitButton
size={'large'}
icon={isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled}
label={isMicEnabled ? love.string.Mute : love.string.UnMute}
showTooltip={{
label: isMicEnabled ? love.string.Mute : love.string.UnMute
}}
action={toggleMicState}
secondIcon={IconUpOutline}
secondAction={micSettings}
separate
/>
{#if allowCam}
<SplitButton
size={'large'}
icon={isCamEnabled ? love.icon.CamEnabled : love.icon.CamDisabled}
label={isCamEnabled ? love.string.StopVideo : love.string.StartVideo}
showTooltip={{
label: isCamEnabled ? love.string.StopVideo : love.string.StartVideo
}}
action={toggleCamState}
secondIcon={IconUpOutline}
secondAction={camSettings}
separate
/>
{/if}
{#if allowShare}
<SplitButton
size={'large'}
icon={$isSharingEnabled ? love.icon.SharingEnabled : love.icon.SharingDisabled}
showTooltip={{ label: $isSharingEnabled ? love.string.StopShare : love.string.Share }}
disabled={($screenSharing && !$isSharingEnabled) || !$lkSessionConnected}
action={changeShare}
secondIcon={IconUpOutline}
secondAction={shareSettings}
separate
/>
{/if}
<div class="flex-between gap-2">
{#if joined && $lkSessionConnected}
<div class="flex-between gap-2">
<MicrophoneButton size="medium" />
<CameraButton size="medium" />
<ShareScreenButton size="medium" on:changeShare={() => dispatch('close')} />
</div>
{/if}
<div style="width: auto" />
{#if canGoBack(joined, $location, $currentMeetingMinutes)}
<ModernButton
icon={roomAccessIcon[room.access]}
label={roomAccessLabel[room.access]}
tooltip={{ label: love.string.ChangeAccess }}
kind={'secondary'}
size={'large'}
disabled={isOffice(room) && room.person !== me}
on:click={setAccess}
icon={IconArrowLeft}
label={love.string.MeetingMinutes}
kind={'primary'}
size={'medium'}
on:click={back}
/>
</div>
{/if}
{#if $location.path[2] !== loveId || (joined && (allowLeave || isMyOffice)) || !joined}
<div class="btns flex-row-center flex-reverse flex-no-shrink w-full flex-gap-2">
{#if joined}
{#if allowLeave}
<ModernButton
label={love.string.LeaveRoom}
icon={love.icon.LeaveRoom}
size={'large'}
kind={'negative'}
on:click={leave}
/>
{:else if isMyOffice}
<ModernButton
label={love.string.EndMeeting}
icon={love.icon.LeaveRoom}
size={'large'}
kind={'negative'}
on:click={end}
/>
{/if}
{:else}
<ModernButton
icon={love.icon.EnterRoom}
label={love.string.EnterRoom}
size={'large'}
kind={'primary'}
autoFocus
on:click={connect}
/>
{/if}
{#if canGoBack(joined, $location, $currentMeetingMinutes)}
<ModernButton
icon={IconArrowLeft}
label={love.string.MeetingMinutes}
kind={'primary'}
size={'large'}
on:click={back}
/>
{/if}
</div>
{/if}
{/if}
{#if joined}
<LeaveRoomButton {room} noLabel={false} size="medium" on:leave={() => dispatch('close')} />
{:else}
<ModernButton
icon={love.icon.EnterRoom}
label={love.string.EnterRoom}
size={'medium'}
kind={'primary'}
autoFocus
on:click={connect}
/>
{/if}
</div>
</div>
<style lang="scss">
@@ -13,12 +13,10 @@
// limitations under the License.
-->
<script lang="ts">
import ui, { Label, ModernToggle } from '@hcengineering/ui'
import { Label, ModernToggle } from '@hcengineering/ui'
import love, { Room } from '@hcengineering/love'
import { getClient } from '@hcengineering/presentation'
import RoomLanguageSelector from './RoomLanguageSelector.svelte'
export let room: Room
const client = getClient()
@@ -0,0 +1,120 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Room, RoomType } from '@hcengineering/love'
import { IconMaximize, ModernButton, Popup, showPopup, TooltipInstance } from '@hcengineering/ui'
import love from '../../plugin'
import { myInfo, myOffice } from '../../stores'
import { isFullScreen, screenSharing } from '../../utils'
import ControlBarContainer from './ControlBarContainer.svelte'
import RoomModal from '../RoomModal.svelte'
import { lkSessionConnected } from '../../liveKitClient'
import MeetingOptionsButton from './controls/MeetingOptionsButton.svelte'
import SendReactionButton from './controls/SendReactionButton.svelte'
import RoomAccessButton from './controls/RoomAccessButton.svelte'
import LeaveRoomButton from './controls/LeaveRoomButton.svelte'
import RecordingButton from './controls/RecordingButton.svelte'
import TranscriptionButton from './controls/TranscriptionButton.svelte'
import MicrophoneButton from './controls/MicrophoneButton.svelte'
import CameraButton from './controls/CameraButton.svelte'
import ShareScreenButton from './controls/ShareScreenButton.svelte'
export let room: Room
export let canMaximize: boolean = true
export let fullScreen: boolean = false
export let onFullScreen: (() => void) | undefined = undefined
let allowLeave: boolean = false
let noLabel: boolean = false
$: allowLeave = $myInfo?.room !== ($myOffice?._id ?? love.ids.Reception)
$: withVideo = $screenSharing || room.type === RoomType.Video
function maximize (): void {
showPopup(RoomModal, { room }, 'full-centered')
}
</script>
<div class="control-bar">
<ControlBarContainer bind:noLabel>
<svelte:fragment slot="right">
{#if room._id !== love.ids.Reception && $lkSessionConnected}
<RoomAccessButton {room} />
{/if}
</svelte:fragment>
<svelte:fragment slot="center">
{#if $lkSessionConnected}
<SendReactionButton />
<MicrophoneButton />
<CameraButton />
<ShareScreenButton />
<RecordingButton {room} />
<TranscriptionButton {room} />
{:else}
<RoomAccessButton {room} />
{/if}
</svelte:fragment>
<svelte:fragment slot="left">
{#if $lkSessionConnected && withVideo && onFullScreen}
<ModernButton
icon={$isFullScreen ? love.icon.ExitFullScreen : love.icon.FullScreen}
tooltip={{
label: $isFullScreen ? love.string.ExitingFullscreenMode : love.string.FullscreenMode,
direction: 'top'
}}
kind={'secondary'}
size={'large'}
on:click={() => {
$isFullScreen = !$isFullScreen
}}
/>
{/if}
{#if $lkSessionConnected && canMaximize}
<ModernButton
icon={IconMaximize}
tooltip={{
label: love.string.FullscreenMode,
direction: 'top'
}}
kind={'secondary'}
iconSize="medium"
size={'large'}
on:click={maximize}
/>
{/if}
<MeetingOptionsButton {room} />
{#if allowLeave}
<LeaveRoomButton {room} {noLabel} />
{/if}
</svelte:fragment>
<svelte:fragment slot="extra">
{#if fullScreen}
<Popup fullScreen />
<TooltipInstance fullScreen />
{/if}
</svelte:fragment>
</ControlBarContainer>
</div>
<style lang="scss">
.control-bar {
width: 100%;
border-top: 1px solid var(--theme-divider-color);
}
</style>
@@ -15,16 +15,7 @@
<script lang="ts">
import { IdMap, Ref, toIdMap } from '@hcengineering/core'
import { getCurrentEmployee } from '@hcengineering/contact'
import {
Invite,
isOffice,
JoinRequest,
Office,
ParticipantInfo,
RequestStatus,
Room,
RoomType
} from '@hcengineering/love'
import { Invite, isOffice, JoinRequest, Office, ParticipantInfo, RequestStatus, Room } from '@hcengineering/love'
import { createQuery, getClient } from '@hcengineering/presentation'
import {
closePopup,
@@ -39,8 +30,8 @@
import workbench from '@hcengineering/workbench'
import { closeWidget, closeWidgetTab, sidebarStore } from '@hcengineering/workbench-resources'
import love from '../plugin'
import { activeInvites, currentRoom, infos, myInfo, myInvites, myOffice, myRequests, rooms } from '../stores'
import love from '../../plugin'
import { activeInvites, currentRoom, infos, myInfo, myInvites, myOffice, myRequests, rooms } from '../../stores'
import {
connectRoom,
createMeetingVideoWidgetTab,
@@ -48,18 +39,17 @@
disconnect,
endMeeting,
getRoomName,
leaveRoom,
screenSharing
} from '../utils'
import ActiveInvitesPopup from './ActiveInvitesPopup.svelte'
import InvitePopup from './InvitePopup.svelte'
import PersonActionPopup from './PersonActionPopup.svelte'
import RequestPopup from './RequestPopup.svelte'
import RequestingPopup from './RequestingPopup.svelte'
import RoomPopup from './RoomPopup.svelte'
import RoomButton from './RoomButton.svelte'
leaveRoom
} from '../../utils'
import ActiveInvitesPopup from '../ActiveInvitesPopup.svelte'
import InvitePopup from '../InvitePopup.svelte'
import PersonActionPopup from '../PersonActionPopup.svelte'
import RequestPopup from '../RequestPopup.svelte'
import RequestingPopup from '../RequestingPopup.svelte'
import RoomPopup from '../RoomPopup.svelte'
import RoomButton from '../RoomButton.svelte'
import { getPersonByPersonRef } from '@hcengineering/contact-resources'
import { lkSessionConnected } from '../liveKitClient'
import { lkSessionConnected } from '../../liveKitClient'
const client = getClient()
@@ -250,11 +240,7 @@
}
}
$: checkActiveVideo(
$location,
$lkSessionConnected && ($currentRoom?.type === RoomType.Video || $screenSharing),
$currentRoom?._id
)
$: checkActiveVideo($location, $lkSessionConnected, $currentRoom?._id)
$: joined = activeRooms.filter((r) => $myInfo?.room === r._id)
@@ -0,0 +1,72 @@
<script lang="ts">
import { DocNavLink } from '@hcengineering/view-resources'
import love, { MeetingMinutes, MeetingStatus, Room } from '@hcengineering/love'
import { onMount } from 'svelte'
import { getClient } from '@hcengineering/presentation'
import { SortingOrder } from '@hcengineering/core'
export let room: Room
const client = getClient()
let currentMeetingMinutes: MeetingMinutes | undefined
$: void client
.findOne(
love.class.MeetingMinutes,
{ attachedTo: room._id, status: MeetingStatus.Active },
{ sort: { createdOn: SortingOrder.Descending } }
)
.then((res) => {
currentMeetingMinutes = res
})
function formatElapsedTime (elapsed: number): string {
const seconds = Math.floor(elapsed / 1000)
const minutes = Math.floor(seconds / 60)
const hours = Math.floor(minutes / 60)
const displaySeconds = (seconds % 60).toString().padStart(2, '0')
const displayMinutes = (minutes % 60).toString().padStart(2, '0')
return hours > 0 ? `${hours}:${displayMinutes}:${displaySeconds}` : `${displayMinutes}:${displaySeconds}`
}
let now = Date.now()
onMount(() => {
const interval = setInterval(() => {
now = Date.now()
}, 1000)
return () => {
clearInterval(interval)
}
})
</script>
{#if currentMeetingMinutes !== undefined}
<div class="flex-between flex-gap-2">
<DocNavLink object={room}>
<span class="font-medium-12 secondary-textColor overflow-label">{room.name}</span>
</DocNavLink>
<!-- elapsed time from start -->
{#if currentMeetingMinutes?.createdOn !== undefined}
{@const elapsed = now - currentMeetingMinutes.createdOn}
<div class="font-medium-12 secondary-textColor">{formatElapsedTime(elapsed)}</div>
{/if}
</div>
{/if}
<div class="flex-between flex-gap-2">
<!-- title -->
{#if currentMeetingMinutes !== undefined}
<DocNavLink object={currentMeetingMinutes}>
<span class="font-medium overflow-label">{currentMeetingMinutes.title}</span>
</DocNavLink>
{:else}
<DocNavLink object={room}>
<span class="font-medium overflow-label">{room.name}</span>
</DocNavLink>
{/if}
</div>
@@ -1,5 +1,8 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte'
import { getBlobRef } from '@hcengineering/presentation'
import { getEmojiByShortCode } from '@hcengineering/emoji-resources'
import { isCustomEmoji } from '@hcengineering/emoji'
export let emoji: string = ''
export let width: number = 0
@@ -13,6 +16,8 @@
let x: number = 0
let size: number = 0
$: customEmoji = getEmojiByShortCode(emoji)
onMount(() => {
offsetX = (Math.random() * width) / 4 - width / 8
size = height / 4
@@ -35,7 +40,15 @@
--duration: {duration}ms;
"
>
{emoji}
{#if customEmoji !== undefined && isCustomEmoji(customEmoji)}
<span class="emoji">
{#await getBlobRef(customEmoji.image) then blobSrc}
<img src={blobSrc.src} alt={emoji} />
{/await}
</span>
{:else}
{emoji}
{/if}
</div>
{/if}
@@ -0,0 +1,37 @@
<script lang="ts">
import { eventToHTMLElement, IconUpOutline, showPopup, SplitButton } from '@hcengineering/ui'
import CamSettingPopup from '../CamSettingPopup.svelte'
import { RoomType } from '@hcengineering/love'
import { currentRoom } from '../../../stores'
import love from '../../../plugin'
import { state, toggleCamState } from '@hcengineering/media-resources'
import view from '@hcengineering/view'
import { getClient } from '@hcengineering/presentation'
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
$: allowCam = $currentRoom?.type === RoomType.Video
$: isCamEnabled = $state.camera?.enabled === true
const client = getClient()
const camKeys = client.getModel().findAllSync(view.class.Action, { _id: love.action.ToggleVideo })?.[0]?.keyBinding
function camSettings (e: MouseEvent): void {
showPopup(CamSettingPopup, {}, eventToHTMLElement(e))
}
</script>
{#if allowCam}
<SplitButton
{size}
icon={isCamEnabled ? love.icon.CamEnabled : love.icon.CamDisabled}
showTooltip={{
label: isCamEnabled ? love.string.StopVideo : love.string.StartVideo,
keys: camKeys
}}
action={toggleCamState}
secondIcon={IconUpOutline}
secondAction={camSettings}
separate
/>
{/if}
@@ -0,0 +1,34 @@
<script lang="ts">
import love from '../../../plugin'
import { ModernButton } from '@hcengineering/ui'
import { endMeeting, leaveRoom } from '../../../utils'
import { infos, myInfo, myOffice, rooms } from '../../../stores'
import { isOffice, Room } from '@hcengineering/love'
import { createEventDispatcher } from 'svelte'
export let room: Room
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
export let noLabel: boolean = false
const dispatch = createEventDispatcher()
$: isMyOffice = isOffice(room) && $myInfo?.room === $myOffice?._id
async function leave (): Promise<void> {
if (isMyOffice && isOffice(room) && $myInfo !== undefined) {
await endMeeting(room, $rooms, $infos, $myInfo)
} else {
await leaveRoom($myInfo, $myOffice)
}
dispatch('leave')
}
</script>
<ModernButton
icon={love.icon.LeaveRoom}
label={noLabel ? undefined : isMyOffice ? love.string.EndMeeting : love.string.LeaveRoom}
tooltip={{ label: isMyOffice ? love.string.EndMeeting : love.string.LeaveRoom, direction: 'top' }}
kind={'negative'}
{size}
on:click={leave}
/>
@@ -0,0 +1,53 @@
<script lang="ts">
import { ButtonMenu, DropdownIntlItem, IconMoreV } from '@hcengineering/ui'
import { Action } from '@hcengineering/view'
import { getResource } from '@hcengineering/platform'
import { lkSessionConnected } from '../../../liveKitClient'
import { getActions } from '@hcengineering/view-resources'
import love from '../../../plugin'
import { Room } from '@hcengineering/love'
import { getClient } from '@hcengineering/presentation'
export let room: Room
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary'
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
let actions: Action[] = []
let moreItems: DropdownIntlItem[] = []
const client = getClient()
$: void getActions(client, room, love.class.Room).then((res) => {
actions = res
})
$: moreItems = actions.map((action) => ({
id: action._id,
label: action.label,
icon: action.icon
}))
async function handleMenuOption (e: CustomEvent<DropdownIntlItem['id']>): Promise<void> {
const action = actions.find((action) => action._id === e.detail)
if (action !== undefined) {
await handleAction(action)
}
}
async function handleAction (action: Action): Promise<void> {
const fn = await getResource(action.action)
await fn(room)
}
</script>
{#if $lkSessionConnected && moreItems.length > 0}
<ButtonMenu
items={moreItems}
icon={IconMoreV}
tooltip={{ label: love.string.MoreOptions, direction: 'top' }}
{kind}
{size}
noSelection
on:selected={handleMenuOption}
/>
{/if}
@@ -0,0 +1,32 @@
<script lang="ts">
import love from '../../../plugin'
import { state, toggleMicState } from '@hcengineering/media-resources'
import { eventToHTMLElement, IconUpOutline, showPopup, SplitButton } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { getClient } from '@hcengineering/presentation'
import MicSettingPopup from '../MicSettingPopup.svelte'
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
$: isMicEnabled = $state.microphone?.enabled === true
const client = getClient()
const micKeys = client.getModel().findAllSync(view.class.Action, { _id: love.action.ToggleMic })?.[0]?.keyBinding
function micSettings (e: MouseEvent): void {
showPopup(MicSettingPopup, {}, eventToHTMLElement(e))
}
</script>
<SplitButton
{size}
icon={isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled}
showTooltip={{
label: isMicEnabled ? love.string.Mute : love.string.UnMute,
keys: micKeys
}}
action={toggleMicState}
secondIcon={IconUpOutline}
secondAction={micSettings}
separate
/>
@@ -0,0 +1,22 @@
<script lang="ts">
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { ModernButton } from '@hcengineering/ui'
import { isRecording, isRecordingAvailable, record } from '../../../utils'
import love from '../../../plugin'
import { lkSessionConnected } from '../../../liveKitClient'
import { Room } from '@hcengineering/love'
export let room: Room
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
</script>
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && $isRecordingAvailable}
<ModernButton
icon={$isRecording ? love.icon.StopRecord : love.icon.Record}
tooltip={{ label: $isRecording ? love.string.StopRecord : love.string.Record }}
disabled={!$lkSessionConnected}
kind={'secondary'}
{size}
on:click={() => record(room)}
/>
{/if}
@@ -0,0 +1,34 @@
<script lang="ts">
import { isOffice, Room, RoomAccess, roomAccessIcon } from '@hcengineering/love'
import love from '../../../plugin'
import { eventToHTMLElement, ModernButton, showPopup } from '@hcengineering/ui'
import RoomAccessPopup from '../../RoomAccessPopup.svelte'
import { getCurrentEmployee } from '@hcengineering/contact'
export let room: Room
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
const me = getCurrentEmployee()
function setAccess (e: MouseEvent): void {
if (isOffice(room) && room.person !== me) return
showPopup(RoomAccessPopup, { room }, eventToHTMLElement(e))
}
</script>
<ModernButton
icon={roomAccessIcon[room.access]}
iconProps={{
fill:
room.access === RoomAccess.Open
? 'var(--bg-positive-default)'
: room.access === RoomAccess.DND
? 'var(--bg-negative-default)'
: 'currentColor'
}}
tooltip={{ label: love.string.ChangeAccess }}
kind={'secondary'}
{size}
disabled={isOffice(room) && room.person !== me}
on:click={setAccess}
/>
@@ -0,0 +1,23 @@
<script lang="ts">
import emojiPlugin from '@hcengineering/emoji'
import { ModernButton, showPopup } from '@hcengineering/ui'
import { lk } from '../../../utils'
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
function addReaction (event: MouseEvent): void {
showPopup(
emojiPlugin.component.EmojiPopup,
{},
event?.target as HTMLElement,
async (result) => {
const emoji = result?.text
if (emoji == null) return
void lk.localParticipant.sendChatMessage(emoji, { topic: 'reaction' })
},
() => {}
)
}
</script>
<ModernButton icon={emojiPlugin.icon.Emoji} kind={'secondary'} {size} on:click={addReaction} />
@@ -0,0 +1,37 @@
<script lang="ts">
import { eventToHTMLElement, IconUpOutline, showPopup, SplitButton } from '@hcengineering/ui'
import { isShareWithSound, isSharingEnabled, liveKitClient, screenSharing } from '../../../utils'
import ShareSettingPopup from '../../ShareSettingPopup.svelte'
import love from '../../../plugin'
import { lkSessionConnected } from '../../../liveKitClient'
import { createEventDispatcher } from 'svelte'
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
const dispatch = createEventDispatcher()
async function changeShare (): Promise<void> {
const newValue = !$isSharingEnabled
const audio = newValue && $isShareWithSound
await liveKitClient.setScreenShareEnabled(newValue, audio)
dispatch('changeShare')
}
function shareSettings (e: MouseEvent): void {
showPopup(ShareSettingPopup, {}, eventToHTMLElement(e))
}
</script>
<SplitButton
{size}
icon={$isSharingEnabled ? love.icon.SharingEnabled : love.icon.SharingDisabled}
iconProps={{
fill: $isSharingEnabled ? 'var(--bg-negative-default)' : 'var(--bg-positive-default)'
}}
showTooltip={{ label: $isSharingEnabled ? love.string.StopShare : love.string.Share }}
disabled={($screenSharing && !$isSharingEnabled) || !$lkSessionConnected}
action={changeShare}
secondIcon={IconUpOutline}
secondAction={shareSettings}
separate
/>
@@ -0,0 +1,29 @@
<script lang="ts">
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { isTranscription, isTranscriptionAllowed, startTranscription, stopTranscription } from '../../../utils'
import { lkSessionConnected } from '../../../liveKitClient'
import love from '../../../plugin'
import view from '@hcengineering/view'
import { ModernButton } from '@hcengineering/ui'
import { Room } from '@hcengineering/love'
export let room: Room
export let size: 'large' | 'medium' | 'small' | 'extra-small' | 'min' = 'large'
</script>
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && isTranscriptionAllowed() && $lkSessionConnected}
<ModernButton
icon={view.icon.Feather}
iconProps={$isTranscription ? { fill: 'var(--button-negative-BackgroundColor)' } : {}}
tooltip={{ label: $isTranscription ? love.string.StopTranscription : love.string.StartTranscription }}
kind="secondary"
{size}
on:click={() => {
if ($isTranscription) {
void stopTranscription(room)
} else {
void startTranscription(room)
}
}}
/>
{/if}
@@ -13,15 +13,13 @@
// limitations under the License.
-->
<script lang="ts">
import love, { MeetingMinutes, Room } from '@hcengineering/love'
import love, { MeetingMinutes } from '@hcengineering/love'
import { ChannelEmbeddedContent } from '@hcengineering/chunter-resources'
import { ActivityMessage } from '@hcengineering/activity'
import { updateTabData, WidgetState } from '@hcengineering/workbench-resources'
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
export let widgetState: WidgetState
export let meetingMinutes: MeetingMinutes
export let room: Room
export let height: string
export let width: string
@@ -43,8 +41,4 @@
on:channel={closeThread}
onReply={replyToThread}
on:close
>
<svelte:fragment slot="header">
<MeetingWidgetHeader doc={meetingMinutes} {room} on:close />
</svelte:fragment>
</ChannelEmbeddedContent>
></ChannelEmbeddedContent>
@@ -18,12 +18,19 @@
import { MeetingMinutes, MeetingStatus, Room } from '@hcengineering/love'
import { Loading } from '@hcengineering/ui'
import love from '../../plugin'
import love from '../../../plugin'
import VideoTab from './VideoTab.svelte'
import { currentMeetingMinutes, currentRoom } from '../../stores'
import { currentMeetingMinutes, currentRoom } from '../../../stores'
import ChatTab from './ChatTab.svelte'
import TranscriptionTab from './TranscriptionTab.svelte'
import { lkSessionConnected } from '../../liveKitClient'
import { lkSessionConnected } from '../../../liveKitClient'
import LeaveRoomButton from '../controls/LeaveRoomButton.svelte'
import RoomAccessButton from '../controls/RoomAccessButton.svelte'
import RecordingButton from '../controls/RecordingButton.svelte'
import TranscriptionButton from '../controls/TranscriptionButton.svelte'
import ControlBarContainer from '../ControlBarContainer.svelte'
import SendReactionButton from '../controls/SendReactionButton.svelte'
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
export let widgetState: WidgetState | undefined
export let height: string
@@ -36,6 +43,8 @@
let room: Room | undefined = undefined
let contentHeight: number = 0
$: room = $currentRoom
$: if (
@@ -70,20 +79,45 @@
}
</script>
{#if widgetState && room}
{#if widgetState.tab === 'video'}
<VideoTab {room} doc={meetingMinutes} on:close={handleClose} />
{:else if widgetState.tab === 'chat'}
{#if !isMeetingMinutesLoaded}
<Loading />
{:else if meetingMinutes}
<ChatTab {meetingMinutes} {room} {widgetState} {height} {width} on:close={handleClose} />
{#if widgetState !== undefined && room}
<div>
<MeetingWidgetHeader doc={meetingMinutes} {room} on:close={handleClose} />
</div>
<div style="height: 100%;" bind:clientHeight={contentHeight}>
{#if widgetState.tab === 'video'}
<VideoTab {room} doc={meetingMinutes} on:close={handleClose} />
{:else if widgetState.tab === 'chat'}
{#if !isMeetingMinutesLoaded}
<Loading />
{:else if meetingMinutes}
<ChatTab {meetingMinutes} {widgetState} height={contentHeight + 'px'} {width} on:close={handleClose} />
{/if}
{:else if widgetState.tab === 'transcription'}
{#if !isMeetingMinutesLoaded}
<Loading />
{:else if meetingMinutes}
<TranscriptionTab
{meetingMinutes}
{room}
{widgetState}
height={contentHeight + 'px'}
{width}
on:close={handleClose}
/>
{/if}
{/if}
{:else if widgetState.tab === 'transcription'}
{#if !isMeetingMinutesLoaded}
<Loading />
{:else if meetingMinutes}
<TranscriptionTab {meetingMinutes} {room} {widgetState} {height} {width} on:close={handleClose} />
{/if}
{/if}
</div>
<ControlBarContainer>
<svelte:fragment slot="right">
<RoomAccessButton {room} size="small" />
</svelte:fragment>
<svelte:fragment slot="center">
<SendReactionButton size="small" />
<RecordingButton {room} size="small" />
<TranscriptionButton {room} size="small" />
</svelte:fragment>
<svelte:fragment slot="left">
<LeaveRoomButton {room} noLabel={true} size="small" />
</svelte:fragment>
</ControlBarContainer>
{/if}
@@ -22,13 +22,13 @@
showPopup,
PopupResult
} from '@hcengineering/ui'
import { MeetingMinutes, Room, RoomType } from '@hcengineering/love'
import { MeetingMinutes, Room } from '@hcengineering/love'
import { onDestroy } from 'svelte'
import love from '../../plugin'
import RoomModal from '../RoomModal.svelte'
import { currentRoom } from '../../stores'
import { screenSharing } from '../../utils'
import love from '../../../plugin'
import RoomModal from '../../RoomModal.svelte'
import { currentRoom } from '../../../stores'
import MeetingOptionsButton from '../controls/MeetingOptionsButton.svelte'
export let room: Room
export let doc: MeetingMinutes | undefined = undefined
@@ -56,8 +56,9 @@
<Header type={'type-aside'} adaptive={'disabled'} closeOnEscape={false} on:close>
<Breadcrumbs items={breadcrumbs} currentOnly />
<svelte:fragment slot="actions">
{#if ($currentRoom !== undefined && $screenSharing) || $currentRoom?.type === RoomType.Video}
{#if $currentRoom !== undefined}
<ButtonIcon icon={IconMaximize} kind="tertiary" size="small" noPrint on:click={maximize} />
<MeetingOptionsButton {room} kind="tertiary" size="small" />
{/if}
</svelte:fragment>
</Header>
@@ -16,7 +16,6 @@
import { MeetingMinutes, Room } from '@hcengineering/love'
import { ChannelEmbeddedContent } from '@hcengineering/chunter-resources'
import { WidgetState } from '@hcengineering/workbench-resources'
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
export let widgetState: WidgetState
export let meetingMinutes: MeetingMinutes
@@ -33,8 +32,4 @@
threadId={undefined}
collection="transcription"
on:close
>
<svelte:fragment slot="header">
<MeetingWidgetHeader doc={meetingMinutes} {room} on:close />
</svelte:fragment>
</ChannelEmbeddedContent>
></ChannelEmbeddedContent>
@@ -15,14 +15,12 @@
<script lang="ts">
import { MeetingMinutes, Room } from '@hcengineering/love'
import VideoPopup from '../VideoPopup.svelte'
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
import VideoPopup from '../../VideoPopup.svelte'
export let room: Room
export let doc: MeetingMinutes | undefined = undefined
</script>
<MeetingWidgetHeader {doc} {room} on:close />
<div class="root">
<VideoPopup room={room._id} isDock />
</div>
@@ -17,11 +17,11 @@
import type { AnySvelteComponent } from '@hcengineering/ui'
import { AppItem } from '@hcengineering/workbench-resources'
import { RoomType } from '@hcengineering/love'
import { currentRoom } from '../../stores'
import { isSharingEnabled } from '../../utils'
import { currentRoom } from '../../../stores'
import { isSharingEnabled } from '../../../utils'
import { state } from '@hcengineering/media-resources'
import love from '../../plugin'
import { lkSessionConnected } from '../../liveKitClient'
import love from '../../../plugin'
import { lkSessionConnected } from '../../../liveKitClient'
export let label: IntlString
export let icon: Asset | AnySvelteComponent
+3 -5
View File
@@ -2,7 +2,7 @@ import { getMetadata, type Resources } from '@hcengineering/platform'
import aiBot from '@hcengineering/ai-bot'
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import ControlExt from './components/ControlExt.svelte'
import ControlExt from './components/meeting/ControlExt.svelte'
import EditMeetingData from './components/EditMeetingData.svelte'
import Main from './components/Main.svelte'
import MeetingData from './components/MeetingData.svelte'
@@ -10,8 +10,8 @@ import SelectScreenSourcePopup from './components/SelectScreenSourcePopup.svelte
import Settings from './components/Settings.svelte'
import WorkbenchExtension from './components/WorkbenchExtension.svelte'
import LoveWidget from './components/LoveWidget.svelte'
import MeetingWidget from './components/widget/MeetingWidget.svelte'
import WidgetSwitcher from './components/widget/WidgetSwitcher.svelte'
import MeetingWidget from './components/meeting/widget/MeetingWidget.svelte'
import WidgetSwitcher from './components/meeting/widget/WidgetSwitcher.svelte'
import MeetingMinutesPresenter from './components/MeetingMinutesPresenter.svelte'
import MeetingMinutesSection from './components/MeetingMinutesSection.svelte'
import EditMeetingMinutes from './components/EditMeetingMinutes.svelte'
@@ -19,7 +19,6 @@ import EditRoom from './components/EditRoom.svelte'
import FloorAttributePresenter from './components/FloorAttributePresenter.svelte'
import FloorView from './components/FloorView.svelte'
import MeetingMinutesTable from './components/MeetingMinutesTable.svelte'
import PanelControlBar from './components/PanelControlBar.svelte'
import RoomPresenter from './components/RoomPresenter.svelte'
import MeetingMinutesDocEditor from './components/MeetingMinutesDocEditor.svelte'
import MeetingMinutesStatusPresenter from './components/MeetingMinutesStatusPresenter.svelte'
@@ -62,7 +61,6 @@ export default async (): Promise<Resources> => ({
FloorAttributePresenter,
FloorView,
MeetingMinutesTable,
PanelControlBar,
RoomPresenter,
MeetingMinutesDocEditor,
MeetingMinutesStatusPresenter,
@@ -208,6 +208,7 @@ export class LiveKitClient {
}
async setCameraEnabled (value: boolean): Promise<void> {
if (!this.currentSessionSupportsVideo) return
try {
await this.liveKitRoom.localParticipant.setCameraEnabled(value)
} catch (e) {
-1
View File
@@ -32,7 +32,6 @@ export default mergeIds(loveId, love, {
FloorAttributePresenter: '' as AnyComponent,
MeetingMinutesTable: '' as AnyComponent,
FloorView: '' as AnyComponent,
PanelControlBar: '' as AnyComponent,
MeetingMinutesDocEditor: '' as AnyComponent,
MeetingMinutesStatusPresenter: '' as AnyComponent,
MeetingScheduleData: '' as AnyComponent,