Call Tile - Render a tile showing that a call was started (#32988)

* Ignore specific directories

Otherwise newly generated screenshots will be ignored.

* Create a CallStartedTileView

This view will render a tile that shows when an EC call was started in
the timeline.

* Add storybook tests

* Add vite tests

* Export the view from shared-components package

* Add a viewmodel for driving the view

* Support rendering the tile in the tile factory

* Fix tile rendering

* Add comment to explain css height

* Use semantic token for gap

* Update snapshot

* Use min-height over height

This will scale the element if the user sets a custom font size.

* Don't show timestamp for call started tile

Timestamp is already shown as a part of the tile content.

* Fix broken tile on bubble layout

The tile should be full-width and not shown within a bubble.

* Fix tests

* Update storybook title
This commit is contained in:
R Midhun Suresh
2026-05-11 21:12:51 +00:00
committed by GitHub
parent c62acc6634
commit 2933b51fea
16 changed files with 448 additions and 1 deletions
@@ -0,0 +1,79 @@
/*
* Copyright 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { BaseViewModel, CallType, type CallStartedTileViewSnapshot } from "@element-hq/web-shared-components";
import type { MatrixEvent } from "matrix-js-sdk/src/matrix";
import type { IRTCNotificationContent } from "matrix-js-sdk/src/matrixrtc";
import SettingsStore from "../../../../../settings/SettingsStore";
import { formatTime } from "../../../../../DateUtils";
import defaultDispatcher from "../../../../../dispatcher/dispatcher";
import type { SettingUpdatedPayload } from "../../../../../dispatcher/payloads/SettingUpdatedPayload";
import type { ActionPayload } from "../../../../../dispatcher/payloads";
import { Action } from "../../../../../dispatcher/actions";
export interface CallStartedTileViewModelProps {
mxEvent: MatrixEvent;
}
function getIntentFromEvent(event: MatrixEvent): CallStartedTileViewSnapshot["type"] {
const content = event.getContent<IRTCNotificationContent>();
const intentInContent = content["m.call.intent"];
switch (intentInContent) {
case "audio":
return CallType.Voice;
case "video":
default:
return CallType.Video;
}
}
function getTimeFromEvent(event: MatrixEvent, showTwelveHour: boolean): CallStartedTileViewSnapshot["timestamp"] {
const content = event.getContent<IRTCNotificationContent>();
const senderTs = content["sender_ts"];
const originServerTs = event.getTs();
const ts = Math.abs(senderTs - originServerTs) > 20000 ? originServerTs : senderTs;
const date = new Date(ts);
const timestamp = formatTime(date, showTwelveHour);
return timestamp;
}
function getInitialSnapshot(event: MatrixEvent): CallStartedTileViewSnapshot {
const type = getIntentFromEvent(event);
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
const timestamp = getTimeFromEvent(event, showTwelveHour);
return { type, timestamp };
}
function isSettingsChangedPayload(payload: ActionPayload): payload is SettingUpdatedPayload {
return payload.action === Action.SettingUpdated;
}
/**
* ViewModel for a timeline tile that indicates the start of an element call.
*/
export class CallStartedTileViewModel extends BaseViewModel<
CallStartedTileViewSnapshot,
CallStartedTileViewModelProps
> {
public constructor(props: CallStartedTileViewModelProps) {
super(props, getInitialSnapshot(props.mxEvent));
SettingsStore.monitorSetting("showTwelveHourTimestamps", null);
const token = defaultDispatcher.register(this.onAction);
this.disposables.track(() => {
defaultDispatcher.unregister(token);
});
}
private onAction = (payload: ActionPayload): void => {
if (!isSettingsChangedPayload(payload) || payload.settingName !== "showTwelveHourTimestamps") return;
const showTwelveHour = (payload.newValue as boolean) ?? false;
const timestamp = getTimeFromEvent(this.props.mxEvent, showTwelveHour);
this.snapshot.merge({ timestamp });
};
}