forked from github/cinny
add scaffolding for widget-based call
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback, useRef, useEffect } from 'react'; // Added useEffect
|
||||||
import { Box, Text, config } from 'folds';
|
import { Box, Text, config } from 'folds';
|
||||||
import { EventType, Room } from 'matrix-js-sdk';
|
import { EventType, Room } from 'matrix-js-sdk';
|
||||||
import { ReactEditor } from 'slate-react';
|
import { ReactEditor } from 'slate-react';
|
||||||
@@ -23,6 +23,9 @@ import { settingsAtom } from '../../state/settings';
|
|||||||
import { useSetting } from '../../state/hooks/settings';
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
import { useAccessibleTagColors, usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
import { useAccessibleTagColors, usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
||||||
import { useTheme } from '../../hooks/useTheme';
|
import { useTheme } from '../../hooks/useTheme';
|
||||||
|
import { logger } from 'matrix-js-sdk/lib/logger';
|
||||||
|
import { ClientWidgetApi, Widget, WidgetKind } from 'matrix-widget-api';
|
||||||
|
import { SmallWidgetDriver } from './SmallWidgetDriver';
|
||||||
|
|
||||||
const FN_KEYS_REGEX = /^F\d+$/;
|
const FN_KEYS_REGEX = /^F\d+$/;
|
||||||
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
||||||
@@ -57,9 +60,38 @@ const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Keep this function to generate the URL
|
||||||
|
const getWidgetUrl = (mx, roomId) => {
|
||||||
|
const baseUrl = window.location.href;
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
embed: "true", // We're embedding EC within another application
|
||||||
|
widgetId: "test",
|
||||||
|
// Template variables are used, so that this can be configured using the data.
|
||||||
|
preload: "$preload", // We want it to load in the background.
|
||||||
|
// skipLobby: "true", // Skip the lobby in case we show a lobby component of our own.
|
||||||
|
returnToLobby: "$returnToLobby", // Returns to the lobby (instead of blank screen) when the call ends. (For video rooms)
|
||||||
|
perParticipantE2EE: "$perParticipantE2EE",
|
||||||
|
hideHeader: "true", // Hide the header since our room header is enough
|
||||||
|
userId: mx.getUserId()!,
|
||||||
|
deviceId: mx.getDeviceId()!,
|
||||||
|
roomId: roomId,
|
||||||
|
baseUrl: window.location.href,
|
||||||
|
parentUrl: baseUrl,
|
||||||
|
// lang: getCurrentLanguage().replace("_", "-"),
|
||||||
|
// fontScale: (FontWatcher.getRootFontSize() / FontWatcher.getBrowserDefaultFontSize()).toString(),
|
||||||
|
theme: "$org.matrix.msc2873.client_theme",
|
||||||
|
});
|
||||||
|
const replacedUrl = params.toString().replace(/%24/g, "$");
|
||||||
|
const url= 'https://elementcall.example.quest' + `#?${replacedUrl}`;
|
||||||
|
logger.error(url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
||||||
const roomInputRef = useRef<HTMLDivElement>(null);
|
const roomInputRef = useRef<HTMLDivElement>(null);
|
||||||
const roomViewRef = useRef<HTMLDivElement>(null);
|
const roomViewRef = useRef<HTMLDivElement>(null);
|
||||||
|
const iframeRef = useRef<HTMLIFrameElement>(null); // Ref for the iframe
|
||||||
|
|
||||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||||
|
|
||||||
@@ -80,31 +112,117 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
|
const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
|
||||||
|
|
||||||
|
const isCall = room.isCallRoom(); // Determine if it's a call room
|
||||||
|
|
||||||
useKeyDown(
|
useKeyDown(
|
||||||
window,
|
window,
|
||||||
useCallback(
|
useCallback(
|
||||||
(evt) => {
|
(evt) => {
|
||||||
if (editableActiveElement()) return;
|
if (editableActiveElement()) return;
|
||||||
if (
|
// Check modal visibility more robustly if needed
|
||||||
document.body.lastElementChild?.className !== 'ReactModalPortal' ||
|
if (document.querySelector('.ReactModalPortal > *')) { // Simple check if any modal portal has content
|
||||||
navigation.isRawModalVisible
|
if (navigation.isRawModalVisible) return; // Skip if raw modal is explicitly visible
|
||||||
) {
|
// Add other modal checks if necessary
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldFocusMessageField(evt) || isKeyHotkey('mod+v', evt)) {
|
if (shouldFocusMessageField(evt) || isKeyHotkey('mod+v', evt)) {
|
||||||
|
// Only focus editor if not in a call view where editor isn't present
|
||||||
|
if (!isCall && editor) {
|
||||||
ReactEditor.focus(editor);
|
ReactEditor.focus(editor);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[editor]
|
[editor, isCall] // Add isCall dependency
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Effect to setup the widget API when the iframe is mounted for a call room
|
||||||
|
useEffect(() => {
|
||||||
|
let widgetApi: ClientWidgetApi | null = null;
|
||||||
|
let driver: SmallWidgetDriver | null = null;
|
||||||
|
|
||||||
|
// Only run setup if it's a call room and the iframe ref is available
|
||||||
|
if (isCall && iframeRef.current) {
|
||||||
|
const iframe = iframeRef.current;
|
||||||
|
const url = getWidgetUrl(mx, roomId);
|
||||||
|
|
||||||
|
// Update iframe src if necessary (though it's set in JSX, this ensures it if URL changes)
|
||||||
|
if (iframe.src !== url) {
|
||||||
|
iframe.src = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Setting up widget API for room ${roomId}`);
|
||||||
|
|
||||||
|
const widget = new Widget({
|
||||||
|
id: 'test-call-widget', // Match ID used in URL params
|
||||||
|
creatorUserId: mx.getUserId()!,
|
||||||
|
type: 'm.custom', // Or appropriate widget type e.g., m.video
|
||||||
|
url: url,
|
||||||
|
roomId: roomId, // Pass roomId if needed by Widget constructor
|
||||||
|
waitForIframeLoad: false,
|
||||||
|
// Add other necessary Widget properties
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure driver is correctly instantiated with necessary parameters
|
||||||
|
// The second argument `[]` might need adjustment based on SmallWidgetDriver's needs (e.g., allowed capabilities)
|
||||||
|
driver = new SmallWidgetDriver(mx, [], widget, WidgetKind.Room, true, roomId);
|
||||||
|
|
||||||
|
widgetApi = new ClientWidgetApi(widget, iframe, driver);
|
||||||
|
// widgetApi.start(); // Start communication if required by your setup
|
||||||
|
|
||||||
|
// Return a cleanup function
|
||||||
|
return () => {
|
||||||
|
logger.info(`Cleaning up widget API for room ${roomId}`);
|
||||||
|
// Implement proper cleanup for ClientWidgetApi and SmallWidgetDriver
|
||||||
|
// This might involve calling stop methods, removing listeners, etc.
|
||||||
|
// Example: widgetApi?.stop();
|
||||||
|
// Example: driver?.stop();
|
||||||
|
widgetApi = null;
|
||||||
|
driver = null;
|
||||||
|
// Clear iframe src to stop loading/activity
|
||||||
|
if (iframeRef.current) {
|
||||||
|
iframeRef.current.src = 'about:blank';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not a call room or the iframe isn't ready, ensure no setup runs/is cleaned up
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
}, [isCall, mx, roomId]); // Dependencies: run effect if call status, client, or room ID changes
|
||||||
|
|
||||||
|
|
||||||
|
// Render Call View
|
||||||
|
if (isCall) {
|
||||||
|
const url = getWidgetUrl(mx, roomId);
|
||||||
|
return (
|
||||||
|
// Attach roomViewRef here if <Page> is the main container you want to reference
|
||||||
|
<Page ref={roomViewRef} style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
<RoomViewHeader />
|
||||||
|
{/* Embed the iframe directly. Ensure parent has definite height or use flex grow */}
|
||||||
|
<Box grow="Yes" style={{ overflow: 'hidden' }}> {/* Use Box with grow */}
|
||||||
|
<iframe
|
||||||
|
ref={iframeRef}
|
||||||
|
src={url} // Set initial source
|
||||||
|
style={{ width: '100%', height: '100%', border: 'none' }}
|
||||||
|
title={`Element Call - ${room.name || roomId}`} // Accessible title
|
||||||
|
// Add necessary sandbox/allow attributes for WebRTC, etc.
|
||||||
|
sandbox="allow-forms allow-scripts allow-same-origin allow-popups allow-downloads"
|
||||||
|
allow="microphone; camera; display-capture; autoplay;"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{/* You might want a minimal footer or status bar here */}
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Standard Text/Timeline Room View
|
||||||
return (
|
return (
|
||||||
<Page ref={roomViewRef}>
|
<Page ref={roomViewRef}>
|
||||||
<RoomViewHeader />
|
<RoomViewHeader />
|
||||||
<Box grow="Yes" direction="Column">
|
<Box grow="Yes" direction="Column">
|
||||||
<RoomTimeline
|
<RoomTimeline
|
||||||
key={roomId}
|
key={roomId} // Key helps React reset state when room changes
|
||||||
room={room}
|
room={room}
|
||||||
eventId={eventId}
|
eventId={eventId}
|
||||||
roomInputRef={roomInputRef}
|
roomInputRef={roomInputRef}
|
||||||
@@ -124,18 +242,17 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{canMessage && (
|
{canMessage ? (
|
||||||
<RoomInput
|
<RoomInput
|
||||||
room={room}
|
room={room}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
fileDropContainerRef={roomViewRef}
|
fileDropContainerRef={roomViewRef} // Pass the Page ref here if needed
|
||||||
ref={roomInputRef}
|
ref={roomInputRef}
|
||||||
getPowerLevelTag={getPowerLevelTag}
|
getPowerLevelTag={getPowerLevelTag}
|
||||||
accessibleTagColors={accessibleTagColors}
|
accessibleTagColors={accessibleTagColors}
|
||||||
/>
|
/>
|
||||||
)}
|
) : (
|
||||||
{!canMessage && (
|
|
||||||
<RoomInputPlaceholder
|
<RoomInputPlaceholder
|
||||||
style={{ padding: config.space.S200 }}
|
style={{ padding: config.space.S200 }}
|
||||||
alignItems="Center"
|
alignItems="Center"
|
||||||
|
|||||||
Reference in New Issue
Block a user