Refactor Jitsi widget to use compound styles.

This commit is contained in:
Half-Shot 2025-10-31 11:28:35 +00:00
parent c7f07f4c29
commit df2fe0feba
4 changed files with 53 additions and 64 deletions

View File

@ -7,16 +7,6 @@
<body>
<div id="jitsiContainer"><!-- the js will put the conference here --></div>
<div id="joinButtonContainer">
<div class="joinConferenceFloating">
<div class="joinConferencePrompt">
<span class="icon"><!-- managed by CSS --></span>
<!-- TODO: i18n -->
<h2>Jitsi Video Conference</h2>
<div id="widgetActionContainer">
<button type="button" id="joinButton">Join Conference</button>
</div>
</div>
</div>
</div>
<!-- This script is not webpacked, and the script is downloaded at build time -->
<script src="./jitsi_external_api.min.js"></script>

View File

@ -6,7 +6,8 @@ Please see LICENSE files in the repository root for full details.
*/
/* TODO: Match the user's theme: https://github.com/element-hq/element-web/issues/12794 */
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound);
@import url("@vector-im/compound-web/dist/style.css");
/* Path to `res` dir in the source tree */
$res: ../../../res;
@ -47,60 +48,26 @@ html {
}
#joinButtonContainer {
display: table;
position: absolute;
height: 100%;
width: 100%;
/* Hidden by default to avoid flashing the prejoin screen at the user when */
/* we're supposed to skip it anyways */
visibility: hidden;
}
.joinConferenceFloating {
display: table-cell;
vertical-align: middle;
height: 100%;
display: grid;
place-items: center;
}
.joinConferencePrompt {
margin-left: auto;
margin-right: auto;
width: 90%;
text-align: center;
}
display: flex;
flex-direction: column;
margin: auto;
width: fit-content;
#joinButton {
/* A mix of AccessibleButton styles */
cursor: pointer;
padding: 7px 18px;
text-align: center;
border-radius: 4px;
display: inline-block;
font-size: 14px;
color: #ffffff;
background-color: #03b381;
border: 0;
}
> * {
margin-left: auto;
margin-right: auto;
.icon {
$icon-size: 42px;
margin-top: -$icon-size; /* to visually center the form */
&::before {
content: "";
background-size: contain;
background-color: $dark-fg;
mask-repeat: no-repeat;
mask-position: center;
mask-image: url("$(res)/img/element-icons/call/video-call.svg");
mask-size: $icon-size;
display: block;
width: $icon-size;
height: $icon-size;
margin: 0 auto; /* center */
}
}
body.theme-light .icon::before {
background-color: $light-fg;
}

View File

@ -32,6 +32,11 @@ import { type IConfigOptions } from "../../IConfigOptions";
import { SnakedObject } from "../../utils/SnakedObject";
import { ElementWidgetCapabilities } from "../../stores/widgets/ElementWidgetCapabilities";
import { getVectorConfig } from "../getconfig";
import React, { JSX, StrictMode, useEffect, useState } from "react";
import { Button } from "@vector-im/compound-web";
import { createRoot } from "react-dom/client";
import { VideoCallIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
interface Config extends _Config {
// Jitsi's types are missing these fields
@ -102,11 +107,43 @@ async function checkAudioVideoEnabled(): Promise<[audioEnabled: boolean, videoEn
return [audioEnabled, videoEnabled];
}
const JoinButtonContainer = ({configPromise}: {configPromise: ReturnType<typeof getVectorConfig>}): JSX.Element => {
const [shouldRender, setShouldRender] = useState(false);
useEffect(() => {
(async () => {
const instanceConfig = new SnakedObject<IConfigOptions>((await configPromise) ?? {} as IConfigOptions);
const jitsiConfig = instanceConfig.get("jitsi_widget");
const skipScreen = new SnakedObject(jitsiConfig ?? {})?.get("skip_built_in_welcome_screen") ?? false;
setShouldRender(!skipScreen);
})();
}, []);
if (!shouldRender) {
return <div></div>;
}
return <>
<div className="joinConferencePrompt">
<VideoCallIcon width={"32px"} height={"32px"}/>
<h2>Jitsi Video Conference</h2>
<div id="widgetActionContainer">
<Button onClick={() => joinConference()}>Join conference</Button>
</div>
</div>
</>
};
const setupCompleted = (async (): Promise<string | void> => {
try {
// Queue a config.json lookup asap, so we can use it later on. We want this to be concurrent with
// other setup work and therefore do not block.
const configPromise = getVectorConfig();
// Render button layout immediately.
const domNode = document.querySelector('#joinButtonContainer')!;
const root = createRoot(domNode);
root.render(<StrictMode><JoinButtonContainer configPromise={configPromise} /></StrictMode>);
// The widget's options are encoded into the fragment to avoid leaking info to the server.
const widgetQuery = new URLSearchParams(window.location.hash.substring(1));
@ -183,6 +220,7 @@ const setupCompleted = (async (): Promise<string | void> => {
void joinConference(audioInput as string | null, videoInput as string | null);
});
handleAction(ElementWidgetActions.HangupCall, async ({ force }) => {
console.log("Got hangup");
if (force === true) {
meetApi?.dispose();
void notifyHangup();
@ -249,7 +287,7 @@ const setupCompleted = (async (): Promise<string | void> => {
supportsScreensharing = qsParam("supportsScreensharing", true) === "true";
// We've reached the point where we have to wait for the config, so do that then parse it.
const instanceConfig = new SnakedObject<IConfigOptions>((await configPromise) ?? <IConfigOptions>{});
const instanceConfig = new SnakedObject<IConfigOptions>((await configPromise) ?? {} as IConfigOptions);
const jitsiConfig = instanceConfig.get("jitsi_widget");
if (jitsiConfig) {
skipOurWelcomeScreen = new SnakedObject(jitsiConfig).get("skip_built_in_welcome_screen") ?? false;
@ -267,18 +305,12 @@ const setupCompleted = (async (): Promise<string | void> => {
if (skipOurWelcomeScreen) {
skipToJitsiSplashScreen();
}
enableJoinButton(); // always enable the button
} catch (e) {
logger.error("Error setting up Jitsi widget", e);
document.getElementById("widgetActionContainer")!.innerText = "Failed to load Jitsi widget";
}
})();
function enableJoinButton(): void {
document.getElementById("joinButton")!.onclick = (): Promise<void> => joinConference();
}
function switchVisibleContainers(): void {
inConference = !inConference;

View File

@ -130,7 +130,7 @@ module.exports = (env, argv) => {
entry: {
bundle: "./src/vector/index.ts",
mobileguide: "./src/vector/mobile_guide/index.ts",
jitsi: "./src/vector/jitsi/index.ts",
jitsi: "./src/vector/jitsi/index.tsx",
usercontent: "./src/usercontent/index.ts",
serviceworker: {
import: "./src/serviceworker/index.ts",