Added fullscreen mode (#5755)

Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
Alexander Platov 2024-06-07 18:30:34 +03:00 committed by GitHub
parent 5e9b9fbf70
commit 3f34143b16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 105 additions and 28 deletions

View File

@ -66,6 +66,18 @@
<path d="M10.6,16.6l3.6-3.6H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h13.2l-3.6-3.6L12,6l6,6l-6,6L10.6,16.6z" /> <path d="M10.6,16.6l3.6-3.6H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h13.2l-3.6-3.6L12,6l6,6l-6,6L10.6,16.6z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M12,2c-1.1,0-2,0.9-2,2v1c0,0.6-0.4,1-1,1S8,5.6,8,5V4c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v16c0,2.2-1.8,4-4,4 h-8c-2.2,0-4-1.8-4-4v-1c0-0.6,0.4-1,1-1s1,0.4,1,1v1c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V4c0-1.1-0.9-2-2-2H12z" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M12,2c-1.1,0-2,0.9-2,2v1c0,0.6-0.4,1-1,1S8,5.6,8,5V4c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v16c0,2.2-1.8,4-4,4 h-8c-2.2,0-4-1.8-4-4v-1c0-0.6,0.4-1,1-1s1,0.4,1,1v1c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V4c0-1.1-0.9-2-2-2H12z" />
</symbol> </symbol>
<symbol id="fullscreen" viewBox="0 0 24 24">
<path d="M22,14.7c-0.5,0-1,0.4-1,1v4.2l-5.7-5.7c-0.4-0.4-1-0.4-1.3,0s-0.4,1,0,1.3l5.7,5.7h-4c-0.5,0-1,0.4-1,1s0.5,0.9,1,0.9H22c0.5,0,1-0.4,1-1v-6.4C23,15.2,22.6,14.7,22,14.7z" />
<path d="M4.2,2.8h4.2c0.5,0,1-0.4,1-1S8.8,1,8.3,1H2C1.4,1,1,1.4,1,2v6.4c0,0.5,0.4,1,1,1s0.9-0.5,0.9-1V4.2l5.7,5.7c0.4,0.4,1,0.4,1.3,0s0.4-1,0-1.3L4.2,2.8z" />
<path d="M22,1h-6.4c-0.5,0-1,0.4-1,1s0.4,1,1,1h4.2l-5.7,5.7c-0.4,0.4-0.4,1,0,1.3s1,0.4,1.3,0l5.7-5.7v4c0,0.5,0.4,1,1,1s0.9-0.5,0.9-1V2C23,1.4,22.6,1,22,1z" />
<path d="M8.6,14.1l-5.7,5.7v-4.2c0-0.5-0.4-1-1-1S1,15.2,1,15.7V22c0,0.6,0.4,1,1,1h6.4c0.5,0,1-0.4,1-1c0-0.6-0.5-0.9-1-0.9H4.2l5.7-5.7c0.4-0.4,0.4-1,0-1.3S8.9,13.7,8.6,14.1z" />
</symbol>
<symbol id="exitfullscreen" viewBox="0 0 24 24">
<path d="M16.9,15.6H21c0.5,0,1-0.4,1-1s-0.4-1-1-1h-6.3c-0.5,0-1,0.4-1,1v6.3c0,0.5,0.4,1,1,1c0.6,0,1-0.4,1-1v-4.1l5.7,5.7c0.4,0.4,1,0.4,1.3,0c0.4-0.4,0.4-1,0-1.3L16.9,15.6z" />
<path d="M9.3,2c-0.6,0-1,0.4-1,0.8V7L2.6,1.3c-0.4-0.4-1-0.4-1.3,0s-0.4,1,0,1.3L7,8.3H2.8C2.4,8.3,2,8.7,2,9.3s0.4,1,1,1h6.3c0.5,0,1-0.4,1-1V2.8C10.1,2.4,9.8,2,9.3,2z" />
<path d="M14.6,10.1h6.3c0.5,0,1-0.4,1-1c0-0.6-0.4-0.8-0.8-0.8h-4.1l5.7-5.7c0.4-0.4,0.4-1,0-1.3c-0.4-0.4-1-0.4-1.3,0L15.6,7V2.8c0-0.5-0.4-1-1-1s-1,0.4-1,1v6.3C13.8,9.8,14.1,10.1,14.6,10.1z" />
<path d="M9.3,13.8H2.8c-0.5,0-1,0.4-1,1c0,0.6,0.5,0.8,1,0.8H7l-5.7,5.7c-0.4,0.4-0.4,1,0,1.3c0.4,0.4,1,0.4,1.3,0l5.7-5.7V21c0,0.5,0.4,1,1,1s1-0.4,1-1v-6.3C10.1,14.1,9.8,13.8,9.3,13.8z" />
</symbol>
<symbol id="record" viewBox="0 0 256 256"> <symbol id="record" viewBox="0 0 256 256">
<path d="M128,24A104,104,0,1,0,232,128,104.11791,104.11791,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.09957,88.09957,0,0,1,128,216Zm72-88a72,72,0,1,1-72-72A72.08124,72.08124,0,0,1,200,128Z"/> <path d="M128,24A104,104,0,1,0,232,128,104.11791,104.11791,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.09957,88.09957,0,0,1,128,216Zm72-88a72,72,0,1,1-72-72A72.08124,72.08124,0,0,1,200,128Z"/>
</symbol> </symbol>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -55,6 +55,8 @@
"Record": "Record", "Record": "Record",
"StopRecord": "Stop record", "StopRecord": "Stop record",
"LeaveRoomConfirmation": "Are you sure you want to leave the room?", "LeaveRoomConfirmation": "Are you sure you want to leave the room?",
"ServiceNotConfigured": "Service is not configured" "ServiceNotConfigured": "Service is not configured",
"FullscreenMode": "Full-screen mode",
"ExitingFullscreenMode": "Exiting fullscreen mode"
} }
} }

View File

@ -55,6 +55,8 @@
"Record": "Grabar", "Record": "Grabar",
"StopRecord": "Detener grabación", "StopRecord": "Detener grabación",
"LeaveRoomConfirmation": "¿Estás seguro de que quieres salir de la sala?", "LeaveRoomConfirmation": "¿Estás seguro de que quieres salir de la sala?",
"ServiceNotConfigured": "El servicio no está configurado" "ServiceNotConfigured": "El servicio no está configurado",
"FullscreenMode": "Modo de pantalla completa",
"ExitingFullscreenMode": "Salir del modo de pantalla completa"
} }
} }

View File

@ -55,6 +55,8 @@
"Record": "Gravar", "Record": "Gravar",
"StopRecord": "Parar gravação", "StopRecord": "Parar gravação",
"LeaveRoomConfirmation": "Tem certeza de que deseja sair da sala?", "LeaveRoomConfirmation": "Tem certeza de que deseja sair da sala?",
"ServiceNotConfigured": "O serviço não está configurado" "ServiceNotConfigured": "O serviço não está configurado",
"FullscreenMode": "Modo de ecrã inteiro",
"ExitingFullscreenMode": "Saindo do modo de tela cheia"
} }
} }

View File

@ -55,6 +55,8 @@
"Record": "Запись", "Record": "Запись",
"StopRecord": "Остановить запись", "StopRecord": "Остановить запись",
"LeaveRoomConfirmation": "Вы уверены, что хотите покинуть комнату?", "LeaveRoomConfirmation": "Вы уверены, что хотите покинуть комнату?",
"ServiceNotConfigured": "Сервис не настроен" "ServiceNotConfigured": "Сервис не настроен",
"FullscreenMode": "Полноэкранный режим",
"ExitingFullscreenMode": "Выход из полноэкранного режима"
} }
} }

View File

@ -34,5 +34,7 @@ loadMetadata(love.icon, {
DND: `${icons}#dnd`, DND: `${icons}#dnd`,
Record: `${icons}#record`, Record: `${icons}#record`,
StopRecord: `${icons}#stopRecord`, StopRecord: `${icons}#stopRecord`,
FullScreen: `${icons}#fullscreen`,
ExitFullScreen: `${icons}#exitfullscreen`,
Invite: `${icons}#invite` Invite: `${icons}#invite`
}) })

View File

@ -20,8 +20,8 @@
import { copyTextToClipboard, getClient } from '@hcengineering/presentation' import { copyTextToClipboard, getClient } from '@hcengineering/presentation'
import { IconUpOutline, ModernButton, SplitButton, eventToHTMLElement, showPopup } from '@hcengineering/ui' import { IconUpOutline, ModernButton, SplitButton, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import view from '@hcengineering/view' import view from '@hcengineering/view'
import { Room, RoomType, isOffice, roomAccessIcon } from '@hcengineering/love' import love, { Room, RoomType, isOffice, roomAccessIcon } from '@hcengineering/love'
import love from '../plugin' import plugin from '../plugin'
import { currentRoom, myInfo, myOffice } from '../stores' import { currentRoom, myInfo, myOffice } from '../stores'
import { import {
isCameraEnabled, isCameraEnabled,
@ -30,6 +30,7 @@
isRecording, isRecording,
isRecordingAvailable, isRecordingAvailable,
isSharingEnabled, isSharingEnabled,
isFullScreen,
leaveRoom, leaveRoom,
record, record,
screenSharing, screenSharing,
@ -48,7 +49,7 @@
let allowLeave: boolean = false let allowLeave: boolean = false
$: allowCam = $currentRoom?.type === RoomType.Video $: allowCam = $currentRoom?.type === RoomType.Video
$: allowLeave = $myInfo?.room !== ($myOffice?._id ?? love.ids.Reception) $: allowLeave = $myInfo?.room !== ($myOffice?._id ?? plugin.ids.Reception)
async function changeMute (): Promise<void> { async function changeMute (): Promise<void> {
await setMic(!$isMicEnabled) await setMic(!$isMicEnabled)
@ -97,15 +98,15 @@
const client = getClient() const client = getClient()
const camKeys = client.getModel().findAllSync(view.class.Action, { _id: love.action.ToggleVideo })?.[0]?.keyBinding const camKeys = client.getModel().findAllSync(view.class.Action, { _id: plugin.action.ToggleVideo })?.[0]?.keyBinding
const micKeys = client.getModel().findAllSync(view.class.Action, { _id: love.action.ToggleMic })?.[0]?.keyBinding const micKeys = client.getModel().findAllSync(view.class.Action, { _id: plugin.action.ToggleMic })?.[0]?.keyBinding
</script> </script>
<div class="bar w-full flex-center flex-gap-2 flex-no-shrink"> <div class="bar w-full flex-center flex-gap-2 flex-no-shrink">
{#if room._id !== love.ids.Reception} {#if room._id !== plugin.ids.Reception}
<ModernButton <ModernButton
icon={roomAccessIcon[room.access]} icon={roomAccessIcon[room.access]}
tooltip={{ label: love.string.ChangeAccess }} tooltip={{ label: plugin.string.ChangeAccess }}
kind={'secondary'} kind={'secondary'}
size={'large'} size={'large'}
disabled={isOffice(room) && room.person !== me} disabled={isOffice(room) && room.person !== me}
@ -115,8 +116,8 @@
{#if $isConnected} {#if $isConnected}
<SplitButton <SplitButton
size={'large'} size={'large'}
icon={$isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled} icon={$isMicEnabled ? plugin.icon.MicEnabled : plugin.icon.MicDisabled}
showTooltip={{ label: $isMicEnabled ? love.string.Mute : love.string.UnMute, keys: micKeys }} showTooltip={{ label: $isMicEnabled ? plugin.string.Mute : plugin.string.UnMute, keys: micKeys }}
action={changeMute} action={changeMute}
secondIcon={IconUpOutline} secondIcon={IconUpOutline}
secondAction={micSettings} secondAction={micSettings}
@ -125,8 +126,8 @@
{#if allowCam} {#if allowCam}
<SplitButton <SplitButton
size={'large'} size={'large'}
icon={$isCameraEnabled ? love.icon.CamEnabled : love.icon.CamDisabled} icon={$isCameraEnabled ? plugin.icon.CamEnabled : plugin.icon.CamDisabled}
showTooltip={{ label: $isCameraEnabled ? love.string.StopVideo : love.string.StartVideo, keys: camKeys }} showTooltip={{ label: $isCameraEnabled ? plugin.string.StopVideo : plugin.string.StartVideo, keys: camKeys }}
disabled={!$isConnected} disabled={!$isConnected}
action={changeCam} action={changeCam}
secondIcon={IconUpOutline} secondIcon={IconUpOutline}
@ -136,8 +137,8 @@
{/if} {/if}
{#if allowShare} {#if allowShare}
<ModernButton <ModernButton
icon={$isSharingEnabled ? love.icon.SharingEnabled : love.icon.SharingDisabled} icon={$isSharingEnabled ? plugin.icon.SharingEnabled : plugin.icon.SharingDisabled}
tooltip={{ label: $isSharingEnabled ? love.string.StopShare : love.string.Share }} tooltip={{ label: $isSharingEnabled ? plugin.string.StopShare : plugin.string.Share }}
disabled={($screenSharing && !$isSharingEnabled) || !$isConnected} disabled={($screenSharing && !$isSharingEnabled) || !$isConnected}
kind={'secondary'} kind={'secondary'}
size={'large'} size={'large'}
@ -146,8 +147,8 @@
{/if} {/if}
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && $isRecordingAvailable} {#if hasAccountRole(getCurrentAccount(), AccountRole.User) && $isRecordingAvailable}
<ModernButton <ModernButton
icon={$isRecording ? love.icon.StopRecord : love.icon.Record} icon={$isRecording ? plugin.icon.StopRecord : plugin.icon.Record}
tooltip={{ label: $isRecording ? love.string.StopRecord : love.string.Record }} tooltip={{ label: $isRecording ? plugin.string.StopRecord : plugin.string.Record }}
disabled={!$isConnected} disabled={!$isConnected}
kind={'secondary'} kind={'secondary'}
size={'large'} size={'large'}
@ -156,10 +157,24 @@
{/if} {/if}
{/if} {/if}
<div class="bar__left-panel flex-gap-2 flex-center"> <div class="bar__left-panel flex-gap-2 flex-center">
{#if $isConnected}
<ModernButton
icon={$isFullScreen ? love.icon.ExitFullScreen : love.icon.FullScreen}
tooltip={{
label: $isFullScreen ? plugin.string.ExitingFullscreenMode : plugin.string.FullscreenMode,
direction: 'top'
}}
kind={'secondary'}
size={'large'}
on:click={() => {
$isFullScreen = !$isFullScreen
}}
/>
{/if}
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)} {#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
<ModernButton <ModernButton
icon={view.icon.Copy} icon={view.icon.Copy}
tooltip={{ label: !linkCopied ? love.string.CopyGuestLink : view.string.Copied, direction: 'top' }} tooltip={{ label: !linkCopied ? plugin.string.CopyGuestLink : view.string.Copied, direction: 'top' }}
kind={'secondary'} kind={'secondary'}
size={'large'} size={'large'}
on:click={copyGuestLink} on:click={copyGuestLink}
@ -167,9 +182,9 @@
{/if} {/if}
{#if allowLeave} {#if allowLeave}
<ModernButton <ModernButton
icon={love.icon.LeaveRoom} icon={plugin.icon.LeaveRoom}
label={love.string.LeaveRoom} label={plugin.string.LeaveRoom}
tooltip={{ label: love.string.LeaveRoom, direction: 'top' }} tooltip={{ label: plugin.string.LeaveRoom, direction: 'top' }}
kind={'negative'} kind={'negative'}
size={'large'} size={'large'}
on:click={leave} on:click={leave}

View File

@ -65,6 +65,7 @@
isCurrentInstanceConnected, isCurrentInstanceConnected,
isMicEnabled, isMicEnabled,
isSharingEnabled, isSharingEnabled,
isFullScreen,
leaveRoom, leaveRoom,
screenSharing, screenSharing,
setCam, setCam,
@ -392,6 +393,13 @@
size={'small'} size={'small'}
action={changeShare} action={changeShare}
/> />
<ActionIcon
icon={$isFullScreen ? love.icon.ExitFullScreen : love.icon.FullScreen}
size={'small'}
action={() => {
$isFullScreen = !$isFullScreen
}}
/>
{/if} {/if}
{#if allowLeave} {#if allowLeave}
<ActionIcon <ActionIcon

View File

@ -17,7 +17,7 @@
import { personByIdStore } from '@hcengineering/contact-resources' import { personByIdStore } from '@hcengineering/contact-resources'
import { Room as TypeRoom } from '@hcengineering/love' import { Room as TypeRoom } from '@hcengineering/love'
import { getMetadata } from '@hcengineering/platform' import { getMetadata } from '@hcengineering/platform'
import { Label, Loading, deviceOptionsStore as deviceInfo, resizeObserver } from '@hcengineering/ui' import { Label, Loading, resizeObserver } from '@hcengineering/ui'
import { import {
LocalParticipant, LocalParticipant,
LocalTrackPublication, LocalTrackPublication,
@ -32,7 +32,15 @@
import { onDestroy, onMount, tick } from 'svelte' import { onDestroy, onMount, tick } from 'svelte'
import love from '../plugin' import love from '../plugin'
import { currentRoom, infos, invites, myInfo, myRequests } from '../stores' import { currentRoom, infos, invites, myInfo, myRequests } from '../stores'
import { awaitConnect, isConnected, isCurrentInstanceConnected, lk, screenSharing, tryConnect } from '../utils' import {
awaitConnect,
isConnected,
isCurrentInstanceConnected,
isFullScreen,
lk,
screenSharing,
tryConnect
} from '../utils'
import ControlBar from './ControlBar.svelte' import ControlBar from './ControlBar.svelte'
import ParticipantView from './ParticipantView.svelte' import ParticipantView from './ParticipantView.svelte'
@ -50,8 +58,7 @@
let participants: ParticipantData[] = [] let participants: ParticipantData[] = []
const participantElements: ParticipantView[] = [] const participantElements: ParticipantView[] = []
let screen: HTMLVideoElement let screen: HTMLVideoElement
let roomEl: HTMLDivElement
const remToPx = (rem: number): number => rem * $deviceInfo.fontSize
function handleTrackSubscribed ( function handleTrackSubscribed (
track: RemoteTrack, track: RemoteTrack,
@ -252,6 +259,7 @@
lk.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed) lk.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
lk.on(RoomEvent.LocalTrackPublished, handleLocalTrack) lk.on(RoomEvent.LocalTrackPublished, handleLocalTrack)
lk.on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnsubscribed) lk.on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnsubscribed)
roomEl && roomEl.addEventListener('fullscreenchange', handleFullScreen)
loading = false loading = false
}) })
@ -290,6 +298,7 @@
lk.off(RoomEvent.TrackMuted, muteHandler) lk.off(RoomEvent.TrackMuted, muteHandler)
lk.off(RoomEvent.TrackUnmuted, muteHandler) lk.off(RoomEvent.TrackUnmuted, muteHandler)
lk.off(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnsubscribed) lk.off(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnsubscribed)
roomEl.removeEventListener('fullscreenchange', handleFullScreen)
}) })
function updateStyle (count: number, screenSharing: boolean): void { function updateStyle (count: number, screenSharing: boolean): void {
@ -297,9 +306,27 @@
rows = Math.ceil(count / columns) rows = Math.ceil(count / columns)
gridStyle = `grid-template-columns: repeat(${columns}, 1fr); aspect-ratio: ${columns * 1280}/${rows * 720};` gridStyle = `grid-template-columns: repeat(${columns}, 1fr); aspect-ratio: ${columns * 1280}/${rows * 720};`
} }
const handleFullScreen = () => ($isFullScreen = document.fullscreenElement != null)
function toggleFullscreen () {
if (!document.fullscreenElement) {
roomEl
.requestFullscreen()
.then(() => ($isFullScreen = true))
.catch((err) => {
console.log(`Error attempting to enable fullscreen mode: ${err.message} (${err.name})`)
$isFullScreen = false
})
} else {
document.exitFullscreen()
$isFullScreen = false
}
}
$: if (((document.fullscreenElement && !$isFullScreen) || $isFullScreen) && roomEl) toggleFullscreen()
</script> </script>
<div class="flex-col-center w-full h-full"> <div bind:this={roomEl} class="flex-col-center w-full h-full" class:theme-dark={$isFullScreen}>
{#if $isConnected && !$isCurrentInstanceConnected} {#if $isConnected && !$isCurrentInstanceConnected}
<div class="flex justify-center error h-full w-full clear-mins"> <div class="flex justify-center error h-full w-full clear-mins">
<Label label={love.string.AnotherWindowError} /> <Label label={love.string.AnotherWindowError} />

View File

@ -67,6 +67,8 @@ export default mergeIds(loveId, love, {
Record: '' as IntlString, Record: '' as IntlString,
StopRecord: '' as IntlString, StopRecord: '' as IntlString,
ServiceNotConfigured: '' as IntlString, ServiceNotConfigured: '' as IntlString,
FullscreenMode: '' as IntlString,
ExitingFullscreenMode: '' as IntlString,
Invite: '' as IntlString, Invite: '' as IntlString,
KnockAction: '' as IntlString KnockAction: '' as IntlString
} }

View File

@ -116,6 +116,7 @@ export const isRecordingAvailable = writable<boolean | undefined>(undefined)
export const isMicEnabled = writable<boolean>(false) export const isMicEnabled = writable<boolean>(false)
export const isCameraEnabled = writable<boolean>(false) export const isCameraEnabled = writable<boolean>(false)
export const isSharingEnabled = writable<boolean>(false) export const isSharingEnabled = writable<boolean>(false)
export const isFullScreen = writable<boolean>(false)
function handleTrackSubscribed ( function handleTrackSubscribed (
track: RemoteTrack, track: RemoteTrack,

View File

@ -135,6 +135,8 @@ const love = plugin(loveId, {
DND: '' as Asset, DND: '' as Asset,
Record: '' as Asset, Record: '' as Asset,
StopRecord: '' as Asset, StopRecord: '' as Asset,
FullScreen: '' as Asset,
ExitFullScreen: '' as Asset,
Invite: '' as Asset Invite: '' as Asset
}, },
metadata: { metadata: {