🚸(frontend) add warning for full screen capture

Following user feedback about infinite loops risks, add warning overlay
when users attempt to share their entire screen. The warning explains
the risk and suggests sharing a specific tab instead, providing options
to stop sharing or dismiss the warning while respecting user preferences
for future sessions.
This commit is contained in:
lebaudantoine
2025-02-13 16:16:05 +01:00
committed by aleb_the_flash
parent b7fb6b1b69
commit 6f09eb3602
6 changed files with 147 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
import { css } from '@/styled-system/css'
import { Button, Text } from '@/primitives'
import { useMemo, useRef } from 'react'
import { ScreenSharePreferenceStore } from '@/stores/ScreenSharePreferences'
import { useSnapshot } from 'valtio'
import { useLocalParticipant } from '@livekit/components-react'
import { useSize } from '../hooks/useResizeObserver'
import { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { useTranslation } from 'react-i18next'
export const FullScreenShareWarning = ({
trackReference,
}: {
trackReference: TrackReferenceOrPlaceholder
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'fullScreenWarning' })
const warningContainerRef = useRef<HTMLDivElement>(null)
const { width: containerWidth } = useSize(warningContainerRef)
const { localParticipant } = useLocalParticipant()
const screenSharePreferences = useSnapshot(ScreenSharePreferenceStore)
const isFullScreenCapture = useMemo(() => {
const trackLabel =
trackReference.publication?.track?.mediaStreamTrack?.label
return trackLabel?.includes('screen')
}, [trackReference])
const shouldShowWarning =
screenSharePreferences.enabled && isFullScreenCapture
const handleStopScreenShare = async () => {
if (!localParticipant.isScreenShareEnabled) return
await localParticipant.setScreenShareEnabled(false, {}, {})
}
const handleDismissWarning = () => {
ScreenSharePreferenceStore.enabled = false
}
if (!shouldShowWarning) return null
return (
<div
className={css({
position: 'absolute',
zIndex: '1000',
height: '100%',
width: '100%',
})}
ref={warningContainerRef}
>
{(!containerWidth || containerWidth >= 300) && (
<div
className={css({
position: 'absolute',
zIndex: '1000',
height: '100%',
width: '100%',
backgroundColor: 'rgba(22, 22, 34, 0.9)',
padding: '2rem',
})}
>
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'column',
gap: '1rem',
md: {
flexDirection: 'row',
},
})}
>
<Text
style={{
color: 'white',
flexBasis: '55%',
fontWeight: '500',
}}
margin={false}
wrap={'pretty'}
>
{t('message')}
</Text>
<div
className={css({
display: 'flex',
flexDirection: 'row',
gap: '1rem',
})}
>
<Button
variant="tertiary"
size="sm"
style={{
height: 'fit-content',
}}
onPress={async () => {
await handleStopScreenShare()
}}
>
{t('stop')}
</Button>
<Button
variant="primaryTextDark"
size="sm"
style={{
height: 'fit-content',
}}
onPress={() => handleDismissWarning()}
>
{t('ignore')}
</Button>
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -28,6 +28,7 @@ import { HStack } from '@/styled-system/jsx'
import { MutedMicIndicator } from './MutedMicIndicator'
import { ParticipantPlaceholder } from './ParticipantPlaceholder'
import { ParticipantTileFocus } from './ParticipantTileFocus'
import { FullScreenShareWarning } from './FullScreenShareWarning'
export function TrackRefContextIfNeeded(
props: React.PropsWithChildren<{
@@ -100,6 +101,7 @@ export const ParticipantTile: (
<div ref={ref} style={{ position: 'relative' }} {...elementProps}>
<TrackRefContextIfNeeded trackRef={trackReference}>
<ParticipantContextIfNeeded participant={trackReference.participant}>
<FullScreenShareWarning trackReference={trackReference} />
{children ?? (
<>
{isTrackReference(trackReference) &&

View File

@@ -199,5 +199,10 @@
"effects": "",
"muteParticipant": "",
"fullScreen": ""
},
"fullScreenWarning": {
"message": "",
"stop": "",
"ignore": ""
}
}

View File

@@ -198,5 +198,10 @@
"effects": "Apply visual effects",
"muteParticipant": "Mute {{name}}",
"fullScreen": "Full screen"
},
"fullScreenWarning": {
"message": "To avoid infinite loop display, do not share your entire screen or browser window. Instead, share a tab or another window.",
"stop": "Stop presenting",
"ignore": "Ignore"
}
}

View File

@@ -198,5 +198,10 @@
"effects": "Appliquer des effets",
"muteParticipant": "Couper le micro de {{name}}",
"fullScreen": "Plein écran"
},
"fullScreenWarning": {
"message": "Pour éviter l'affichage en boucle infinie, ne partagez pas l'intégralité de votre écran ni de votre fenêtre de navigateur. Partagez plutôt un onglet ou une autre fenêtre.",
"stop": "Arrêter la présentation",
"ignore": "Ignorer"
}
}

View File

@@ -0,0 +1,9 @@
import { proxy } from 'valtio'
type State = {
enabled: boolean
}
export const ScreenSharePreferenceStore = proxy<State>({
enabled: true,
})