Files
element-call/src/useTheme.ts
T
Lotus CIandClaude Opus 5.5 0f93368a7e feat(lotus): in-frame screenshare button where the host can't delegate (cinny #43)
Firefox, Safari and WebKitGTK have no Capability Delegation, so the host
can't start a share from its call bar: getDisplayMedia needs the click in
this frame. Until now the host clicked EC's hidden footer button through
the DOM, which only works while the frame is same-origin.

With lotusHostControls + lotusFrameScreenshare the fork now shows EC's own
screenshare button (floating bottom-right, footer still hidden), so the
click is native. controls_state reports frameScreenshare so the host hides
its own button. New toWidget io.lotus.set_frame_screenshare { visible }
lets the host hide it while the room's call policy forbids screensharing
(never while a share is live, so it can still be stopped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-26 21:48:22 -04:00

93 lines
3.7 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { WidgetApiToWidgetAction } from "matrix-widget-api";
import { type IThemeChangeActionRequest } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/lib/logger";
import { getUrlParams } from "./UrlParams";
import { widget } from "./widget";
import { lotusFlag } from "./lotus/lotusWidget";
export const useTheme = (): void => {
const [requestedTheme, setRequestedTheme] = useState(
() => getUrlParams().theme,
);
const previousTheme = useRef<string | null>(document.body.classList.item(0));
useEffect(() => {
if (widget) {
const onThemeChange = (
ev: CustomEvent<IThemeChangeActionRequest>,
): void => {
ev.preventDefault();
if ("name" in ev.detail.data && typeof ev.detail.data.name === "string")
setRequestedTheme(ev.detail.data.name);
widget!.api.transport.reply(ev.detail, {});
};
widget.lazyActions.on(WidgetApiToWidgetAction.ThemeChange, onThemeChange);
return (): void => {
widget!.lazyActions.off(
WidgetApiToWidgetAction.ThemeChange,
onThemeChange,
);
};
}
}, []);
useLayoutEffect(() => {
// If no theme has been explicitly requested we default to dark
const theme = requestedTheme?.includes("light") ? "light" : "dark";
const themeHighContrast = requestedTheme?.includes("high-contrast")
? "-hc"
: "";
const themeString = "cpd-theme-" + theme + themeHighContrast;
if (themeString !== previousTheme.current) {
document.body.classList.remove(
"cpd-theme-light",
"cpd-theme-dark",
"cpd-theme-light-hc",
"cpd-theme-dark-hc",
);
document.body.classList.add(themeString);
previousTheme.current = themeString;
}
document.body.classList.remove("no-theme");
// [lotus #5] Native theming hooks, opted in by the host via URL flags, so
// it no longer has to inject CSS into the iframe after load.
const lotusTransparent = lotusFlag("lotusTransparent");
const lotusTheme = lotusFlag("lotusTheme");
if (lotusTransparent) document.body.classList.add("lotus-transparent");
// [lotus #21] The two flags are only meaningful together: a transparent
// canvas with no matching theme leaves text/icon colours computed against
// the default dark canvas token while the real pixels behind them are
// whatever the host painted. If a host sets lotusTransparent without
// lotusTheme, warn and apply lotusTheme anyway rather than silently
// risking unreadable UI.
if (lotusTransparent && !lotusTheme) {
logger.warn(
"[lotus] lotusTransparent is set without lotusTheme; applying " +
"lotusTheme anyway since the two flags are only meaningful together.",
);
}
if (lotusTheme || lotusTransparent) {
document.body.classList.add("lotus-theme");
// [cinny #43] The frame's root colour scheme must match the host's, or
// the browser paints an opaque backdrop behind the transparent frame.
// (The host used to inject `:root { color-scheme }` for this.)
document.documentElement.style.colorScheme = theme;
}
// [cinny #43] The host draws its own call bar: hide EC's footer. Replaces
// the host's injected styles. Where the host can't start a share for us,
// LotusFrameScreenshare shows EC's screenshare button on its own.
if (lotusFlag("lotusHostControls"))
document.body.classList.add("lotus-host-controls");
}, [previousTheme, requestedTheme]);
};