Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
to = "/assets/:splat"
status = 200

[[redirects]]
from = "/widgets/*"
to = "/widgets/:splat"
status = 200

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to update the nginx config alongside this. Apparently it was still not working when I deployed it on my server.

[[redirects]]
from = "/*"
to = "/index.html"
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
"@element-hq/element-call-embedded": "0.16.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"@element-hq/element-call-embedded": "0.16.0",
"@element-hq/element-call-embedded": "0.16.3",

"@fontsource/inter": "4.5.14",
"@tanstack/react-query": "5.24.1",
"@tanstack/react-query-devtools": "5.24.1",
Expand Down Expand Up @@ -55,6 +56,7 @@
"linkify-react": "4.1.3",
"linkifyjs": "4.1.3",
"matrix-js-sdk": "38.2.0",
"matrix-widget-api": "1.13.1",

@wrenix wrenix Feb 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"matrix-widget-api": "1.13.1",
"matrix-widget-api": "1.17.0",

"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
"prismjs": "1.30.0",
Expand Down
151 changes: 151 additions & 0 deletions src/app/components/element-call/CallView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, { useEffect, useRef, useState } from 'react';
import { ClientWidgetApi } from 'matrix-widget-api';
import { Button, Text } from 'folds';
import ElementCall from './ElementCall';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useEventEmitter } from './utils';
import { useIsDirectRoom, useRoom } from '../../hooks/useRoom';
import { useCallOngoing } from '../../hooks/useCallOngoing';

export enum CallWidgetActions {
// All of these actions are currently specific to Jitsi and Element Call
JoinCall = 'io.element.join',
HangupCall = 'im.vector.hangup',
Close = 'io.element.close',
}
export function action(type: CallWidgetActions): string {
return `action:${type}`;
}

const iframeFeatures =
'microphone; camera; encrypted-media; autoplay; display-capture; clipboard-write; ' +
'clipboard-read;';
const sandboxFlags =
'allow-forms allow-popups allow-popups-to-escape-sandbox ' +
'allow-same-origin allow-scripts allow-presentation allow-downloads';
const iframeStyles = { flex: '1 1', border: 'none' };
const containerStyle = (hidden: boolean): React.CSSProperties => ({
display: 'flex',
flexDirection: 'column',
position: 'relative',
height: '100%',
width: '100%',
...(hidden
? {
overflow: 'hidden',
width: 0,
height: 0,
}
: {}),
});
const closeButtonStyle: React.CSSProperties = {
position: 'absolute',
top: 8,
right: 8,
zIndex: 100,
maxWidth: 'fit-content',
};

enum State {
Preparing = 'preparing',
Lobby = 'lobby',
Joined = 'joined',
HungUp = 'hung_up',
CanClose = 'can_close',
}

/**
* Shows a call for this room. Rendering this component will
* automatically create a call widget and join the call in the room.
* @returns
*/
export interface IRoomCallViewProps {
onClose?: () => void;
onJoin?: () => void;
onHangup?: (errorMessage?: string) => void;
}

export function CallView({
onJoin = undefined,
onClose = undefined,
onHangup = undefined,
}: IRoomCallViewProps): JSX.Element {
// Construct required variables
const room = useRoom();
const client = useMatrixClient();
const iframe = useRef<HTMLIFrameElement>(null);

// Model state
const [elementCall, setElementCall] = useState<ElementCall | null>();
const [widgetApi, setWidgetApi] = useState<ClientWidgetApi | null>(null);
const [state, setState] = useState(State.Preparing);

// Initialization parameters
const isDirect = useIsDirectRoom();
const callOngoing = useCallOngoing(room);
const initialCallOngoing = React.useRef(callOngoing);
const initialIsDirect = React.useRef(isDirect);
useEffect(() => {
if (client && room && !elementCall) {
const e = new ElementCall(client, room, initialIsDirect.current, initialCallOngoing.current);
setElementCall(e);
}
}, [client, room, setElementCall, elementCall]);

// Start the messaging over the widget api.
useEffect(() => {
if (iframe.current && elementCall) {
elementCall.startMessaging(iframe.current);
}
return () => {
elementCall?.stopMessaging();
};
}, [iframe, elementCall]);

// Widget api ready
useEventEmitter(elementCall, 'ready', () => {
setWidgetApi(elementCall?.widgetApi ?? null);
setState(State.Lobby);
});

// Use widget api to listen for hangup/join/close actions
useEventEmitter(widgetApi, action(CallWidgetActions.HangupCall), () => {
setState(State.HungUp);
onHangup?.();
});
useEventEmitter(widgetApi, action(CallWidgetActions.JoinCall), () => {
setState(State.Joined);
onJoin?.();
});
useEventEmitter(widgetApi, action(CallWidgetActions.Close), () => {
setState(State.CanClose);
onClose?.();
});

// render component
return (
<div style={containerStyle(state === State.HungUp)}>
{/* Exit button for lobby state */}
{state === State.Lobby && (
<Button
variant="Secondary"
onClick={() => {
setState(State.CanClose);
onClose?.();
}}
style={closeButtonStyle}
>
<Text size="B400">Close</Text>
</Button>
)}
<iframe
ref={iframe}
allow={iframeFeatures}
sandbox={sandboxFlags}
style={iframeStyles}
src={elementCall?.embedUrl}
title="room call"
/>
</div>
);
}
Loading