103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
/*
|
|||
|
|
Copyright 2026 Lotus Guild
|
||
|
|
|
||
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||
|
|
Please see LICENSE in the repository root for full details.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { type FC, useCallback, useEffect, useId, useRef } from "react";
|
||
|
|
import { Button, Text } from "@vector-im/compound-web";
|
||
|
|
|
||
|
|
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||
|
|
import { useBehavior } from "../useBehavior";
|
||
|
|
import {
|
||
|
|
lotusFrameScreenshareEnabled,
|
||
|
|
lotusFrameScreenshareVisible$,
|
||
|
|
lotusScreensharePrompt$,
|
||
|
|
} from "./lotusControls";
|
||
|
|
import styles from "./LotusScreensharePrompt.module.css";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* [cinny #43] "Share your screen?" asked inside the call frame, for hosts that
|
||
|
|
* can't hand the user's click to this frame (Firefox, Safari, WebKitGTK). The
|
||
|
|
* host's call-bar button sends `io.lotus.prompt_screenshare`; the Share click
|
||
|
|
* lands here, so getDisplayMedia runs with this frame's own activation. Same
|
||
|
|
* two clicks as the host's own confirm on Chromium.
|
||
|
|
*/
|
||
|
|
export const LotusScreensharePrompt: FC<{ vm: CallViewModel }> = ({ vm }) => {
|
||
|
|
const open = useBehavior(lotusScreensharePrompt$);
|
||
|
|
const sharing = useBehavior(vm.sharingScreen$);
|
||
|
|
const allowed = useBehavior(lotusFrameScreenshareVisible$);
|
||
|
|
const shareRef = useRef<HTMLButtonElement>(null);
|
||
|
|
const titleId = useId();
|
||
|
|
const descriptionId = useId();
|
||
|
|
|
||
|
|
const close = useCallback(() => lotusScreensharePrompt$.next(false), []);
|
||
|
|
|
||
|
|
// Stale once a share is running (started some other way) or the room's
|
||
|
|
// policy stops allowing it.
|
||
|
|
useEffect(() => {
|
||
|
|
if (open && (sharing || !allowed)) close();
|
||
|
|
}, [open, sharing, allowed, close]);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (open) shareRef.current?.focus();
|
||
|
|
}, [open]);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!open) return undefined;
|
||
|
|
const onKeyDown = (e: KeyboardEvent): void => {
|
||
|
|
if (e.key !== "Escape") return;
|
||
|
|
e.preventDefault();
|
||
|
|
close();
|
||
|
|
};
|
||
|
|
window.addEventListener("keydown", onKeyDown, true);
|
||
|
|
return (): void => window.removeEventListener("keydown", onKeyDown, true);
|
||
|
|
}, [open, close]);
|
||
|
|
|
||
|
|
const toggle = vm.toggleScreenSharing;
|
||
|
|
if (!open || !lotusFrameScreenshareEnabled() || !toggle) return null;
|
||
|
|
|
||
|
|
const onShare = (): void => {
|
||
|
|
close();
|
||
|
|
// Synchronously inside this click: getDisplayMedia needs its activation.
|
||
|
|
toggle();
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={styles.prompt}
|
||
|
|
role="alertdialog"
|
||
|
|
aria-labelledby={titleId}
|
||
|
|
aria-describedby={descriptionId}
|
||
|
|
data-testid="lotus_screenshare_prompt"
|
||
|
|
>
|
||
|
|
<Text
|
||
|
|
as="h2"
|
||
|
|
id={titleId}
|
||
|
|
size="md"
|
||
|
|
weight="semibold"
|
||
|
|
className={styles.title}
|
||
|
|
>
|
||
|
|
Share your screen?
|
||
|
|
</Text>
|
||
|
|
<Text id={descriptionId} size="md" className={styles.description}>
|
||
|
|
Your screen will be visible to everyone in this call.
|
||
|
|
</Text>
|
||
|
|
<div className={styles.buttons}>
|
||
|
|
<Button
|
||
|
|
ref={shareRef}
|
||
|
|
size="md"
|
||
|
|
onClick={onShare}
|
||
|
|
data-testid="lotus_screenshare_prompt_share"
|
||
|
|
>
|
||
|
|
Share
|
||
|
|
</Button>
|
||
|
|
<Button size="md" kind="secondary" onClick={close}>
|
||
|
|
Cancel
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|