diff --git a/src/vector/jitsi/index.html b/src/vector/jitsi/index.html index 7b60e6a22b..79346cfa65 100644 --- a/src/vector/jitsi/index.html +++ b/src/vector/jitsi/index.html @@ -7,16 +7,6 @@
-
-
- - -

Jitsi Video Conference

-
- -
-
-
diff --git a/src/vector/jitsi/index.pcss b/src/vector/jitsi/index.pcss index 09299f1b7f..6dd234bcaa 100644 --- a/src/vector/jitsi/index.pcss +++ b/src/vector/jitsi/index.pcss @@ -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; } diff --git a/src/vector/jitsi/index.ts b/src/vector/jitsi/index.tsx similarity index 93% rename from src/vector/jitsi/index.ts rename to src/vector/jitsi/index.tsx index a5ea8cba6d..63a3f3fb73 100644 --- a/src/vector/jitsi/index.ts +++ b/src/vector/jitsi/index.tsx @@ -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}): JSX.Element => { + const [shouldRender, setShouldRender] = useState(false); + useEffect(() => { + (async () => { + const instanceConfig = new SnakedObject((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
; + } + + return <> +
+ +

Jitsi Video Conference

+
+ +
+
+ + +}; + const setupCompleted = (async (): Promise => { 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(); + // 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 => { 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 => { 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((await configPromise) ?? {}); + const instanceConfig = new SnakedObject((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 => { 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 => joinConference(); -} - function switchVisibleContainers(): void { inConference = !inConference; diff --git a/webpack.config.js b/webpack.config.js index b979ded2b2..624679f0fd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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",