Merge upstream v4.12.3 (Element Call 0.20.1) into lotus
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,10 +14,14 @@ export class CallControl extends EventEmitter implements CallControlState {
|
||||
|
||||
private iframe: HTMLIFrameElement;
|
||||
|
||||
private bodyMutationObserver: MutationObserver;
|
||||
|
||||
private controlMutationObserver: MutationObserver;
|
||||
|
||||
private _pipMode = false;
|
||||
|
||||
private mediaStatePromiseResolver: undefined | (() => void);
|
||||
|
||||
private get document(): Document | undefined {
|
||||
return this.iframe.contentDocument ?? this.iframe.contentWindow?.document;
|
||||
}
|
||||
@@ -30,17 +34,29 @@ export class CallControl extends EventEmitter implements CallControlState {
|
||||
return screenshareBtn ?? undefined;
|
||||
}
|
||||
|
||||
private get leaveButton(): Element | undefined {
|
||||
const leaveBtn = this.document?.querySelector('[data-testid="incall_leave"]');
|
||||
|
||||
return leaveBtn ?? undefined;
|
||||
}
|
||||
|
||||
private get settingsButton(): HTMLElement | undefined {
|
||||
// EC 0.19.3: settings button has data-testid="settings-bottom-center"
|
||||
return (
|
||||
(this.document?.querySelector('[data-testid="settings-bottom-center"]') as HTMLElement) ??
|
||||
undefined
|
||||
);
|
||||
// EC 0.20.1: settings button moved to bottom-left; fall back to bottom-center.
|
||||
const settingsButtonLeft = this.document?.querySelector(
|
||||
'[data-testid="settings-bottom-left"]',
|
||||
) as HTMLButtonElement | undefined;
|
||||
const settingsButtonCenter = this.document?.querySelector(
|
||||
'[data-testid="settings-bottom-center"]',
|
||||
) as HTMLButtonElement | undefined;
|
||||
|
||||
return settingsButtonLeft ?? settingsButtonCenter ?? undefined;
|
||||
}
|
||||
|
||||
private get reactionsButton(): HTMLElement | undefined {
|
||||
// EC 0.19.3: reactions/raise-hand button has a CSS module class containing "raiseHand"
|
||||
return (this.document?.querySelector('[class*="raiseHand"]') as HTMLElement) ?? undefined;
|
||||
// EC 0.20.1: reactions/raise-hand button sits just before the leave button.
|
||||
const reactionsButton = this.leaveButton?.previousElementSibling as HTMLElement | null;
|
||||
|
||||
return reactionsButton ?? undefined;
|
||||
}
|
||||
|
||||
private get spotlightButton(): HTMLInputElement | undefined {
|
||||
@@ -66,6 +82,7 @@ export class CallControl extends EventEmitter implements CallControlState {
|
||||
this.call = call;
|
||||
this.iframe = iframe;
|
||||
|
||||
this.bodyMutationObserver = new MutationObserver(this.onBodyMutation.bind(this));
|
||||
this.controlMutationObserver = new MutationObserver(this.onControlMutation.bind(this));
|
||||
}
|
||||
|
||||
@@ -118,6 +135,30 @@ export class CallControl extends EventEmitter implements CallControlState {
|
||||
}
|
||||
|
||||
public startObserving() {
|
||||
if (!this.document) return;
|
||||
|
||||
this.bodyMutationObserver.observe(this.document.body, {
|
||||
childList: true,
|
||||
subtree: false, // only direct children of body
|
||||
});
|
||||
this.onBodyMutation();
|
||||
}
|
||||
|
||||
private onBodyMutation() {
|
||||
if (!this.document) return;
|
||||
|
||||
this.document.body.style.setProperty('background', 'none', 'important');
|
||||
|
||||
const controls = this.leaveButton?.parentElement?.parentElement;
|
||||
if (controls) {
|
||||
controls.style.setProperty('position', 'absolute');
|
||||
controls.style.setProperty('visibility', 'hidden');
|
||||
}
|
||||
|
||||
this.observeControls();
|
||||
}
|
||||
|
||||
private observeControls() {
|
||||
this.controlMutationObserver.disconnect();
|
||||
|
||||
const screenshareBtn = this.screenshareButton;
|
||||
@@ -141,8 +182,14 @@ export class CallControl extends EventEmitter implements CallControlState {
|
||||
this.setSound(this.sound);
|
||||
}
|
||||
|
||||
private setMediaState(state: ElementMediaStatePayload) {
|
||||
return this.call.transport.send(ElementWidgetActions.DeviceMute, state);
|
||||
private async setMediaState(state: ElementMediaStatePayload) {
|
||||
const data = await this.call.transport.send(ElementWidgetActions.DeviceMute, state);
|
||||
return new Promise<typeof data>((resolve) => {
|
||||
if (this.mediaStatePromiseResolver) {
|
||||
this.mediaStatePromiseResolver();
|
||||
}
|
||||
this.mediaStatePromiseResolver = () => resolve(data);
|
||||
});
|
||||
}
|
||||
|
||||
private setSound(sound: boolean): void {
|
||||
@@ -186,9 +233,14 @@ export class CallControl extends EventEmitter implements CallControlState {
|
||||
if (this.microphone && !this.sound) {
|
||||
this.toggleSound();
|
||||
}
|
||||
|
||||
if (this.mediaStatePromiseResolver) {
|
||||
this.mediaStatePromiseResolver();
|
||||
this.mediaStatePromiseResolver = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public onControlMutation() {
|
||||
private onControlMutation() {
|
||||
const screenshare: boolean = this.screenshareButton?.getAttribute('data-kind') === 'primary';
|
||||
const spotlight: boolean = this.spotlightButton?.checked ?? false;
|
||||
|
||||
@@ -321,6 +373,7 @@ export class CallControl extends EventEmitter implements CallControlState {
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.bodyMutationObserver.disconnect();
|
||||
this.controlMutationObserver.disconnect();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user