mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-12 18:39:19 +00:00
Merge branch 'livekit' into johannes/docker-compose-renovate
This commit is contained in:
@@ -67,6 +67,7 @@ These parameters are relevant to both [widget](./embedded_standalone.md) and [st
|
||||
| `showControls` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Displays controls like mute, screen-share, invite, and hangup buttons during a call. |
|
||||
| `skipLobby` (deprecated: use `intent` instead) | `true` or `false` | No. If `intent` is explicitly `start_call` then defaults to `true`. Otherwise defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. When `true` the audio and video inputs will be muted by default. (This means there currently is no way to start without muted video if one wants to skip the lobby. Also not in widget mode.) |
|
||||
| `theme` | One of: `light`, `dark`, `light-high-contrast`, `dark-high-contrast` | No, defaults to `dark` | No, defaults to `dark` | UI theme to use. |
|
||||
| `background` | One of: `solid`, `gradient` | No, defaults to `gradient` | No, defaults to `gradient` | Visual style of the page background. |
|
||||
| `viaServers` | Comma separated list of [Matrix Server Names](https://spec.matrix.org/v1.12/appendices/#server-name) | Not applicable | No | Homeserver for joining a room, non-empty value required for rooms not on the user’s default homeserver. |
|
||||
| `sendNotificationType` | `ring` or `notification` | No | No | Will send a "ring" or "notification" `m.rtc.notification` event if the user is the first one in the call. |
|
||||
| `autoLeave` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Whether the app should automatically leave the call when there is no one left in the call. |
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"blur_not_supported_by_browser": "(Background blur is not supported by this device.)",
|
||||
"developer_tab_title": "Developer",
|
||||
"devices": {
|
||||
"activating": "Activating…",
|
||||
"camera": "Camera",
|
||||
"camera_numbered": "Camera {{n}}",
|
||||
"change_device_button": "Change audio device",
|
||||
|
||||
@@ -125,21 +125,10 @@ async function expectVideoTilesCount(page: Page, count: number): Promise<void> {
|
||||
});
|
||||
|
||||
// There should be `count` video elements, visible and autoplaying
|
||||
await expect(page.locator("video")).toHaveCount(count);
|
||||
|
||||
await expect(async () => {
|
||||
const videoBlockCount = await page
|
||||
.locator("video")
|
||||
.evaluateAll(
|
||||
(videos: Element[]) =>
|
||||
videos.filter(
|
||||
(v: Element) => window.getComputedStyle(v).display === "block",
|
||||
).length,
|
||||
);
|
||||
expect(videoBlockCount).toBe(count);
|
||||
}).toPass({
|
||||
timeout: 10000,
|
||||
});
|
||||
await expect(page.locator("video").filter({ visible: true })).toHaveCount(
|
||||
count,
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
}
|
||||
|
||||
export const SpaHelpers = {
|
||||
|
||||
@@ -385,25 +385,9 @@ export class TestHelpers {
|
||||
frame: FrameLocator,
|
||||
count: number,
|
||||
): Promise<void> {
|
||||
// XXX we need to be better at our HTML markup and accessibility, it would make
|
||||
// this kind of stuff way easier to test if we could look out for aria attributes.
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
return await frame
|
||||
.locator("video")
|
||||
.evaluateAll(
|
||||
(videos: Element[]) =>
|
||||
videos.filter(
|
||||
(v: Element) =>
|
||||
window.getComputedStyle(v).display === "block",
|
||||
).length,
|
||||
);
|
||||
},
|
||||
{
|
||||
timeout: 10000,
|
||||
},
|
||||
)
|
||||
.toBe(count);
|
||||
await expect(frame.locator("video").filter({ visible: true })).toHaveCount(
|
||||
count,
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
18
src/App.tsx
18
src/App.tsx
@@ -30,7 +30,7 @@ import { useTheme } from "./useTheme";
|
||||
import { ProcessorProvider } from "./livekit/TrackProcessorContext";
|
||||
import { type AppViewModel } from "./state/AppViewModel";
|
||||
import { MediaDevicesContext } from "./MediaDevicesContext";
|
||||
import { getUrlParams, HeaderStyle } from "./UrlParams";
|
||||
import { getUrlParams, HeaderStyle, useUrlParams } from "./UrlParams";
|
||||
import { AppBar } from "./AppBar";
|
||||
|
||||
const SentryRoute = Sentry.withSentryReactRouterV7Routing(Route);
|
||||
@@ -41,19 +41,17 @@ interface SimpleProviderProps {
|
||||
|
||||
const BackgroundProvider: FC<SimpleProviderProps> = ({ children }) => {
|
||||
const { pathname } = useLocation();
|
||||
const { background } = useUrlParams();
|
||||
|
||||
useEffect(() => {
|
||||
let backgroundImage = "";
|
||||
if (!["/login", "/register"].includes(pathname) && !widget) {
|
||||
backgroundImage = "var(--background-gradient)";
|
||||
}
|
||||
document
|
||||
.getElementsByTagName("body")[0]
|
||||
.setAttribute("data-background", background);
|
||||
}, [pathname, background]);
|
||||
|
||||
document.getElementsByTagName("body")[0].style.backgroundImage =
|
||||
backgroundImage;
|
||||
}, [pathname]);
|
||||
|
||||
return <>{children}</>;
|
||||
return children;
|
||||
};
|
||||
|
||||
const ThemeProvider: FC<SimpleProviderProps> = ({ children }) => {
|
||||
useTheme();
|
||||
return children;
|
||||
|
||||
@@ -45,6 +45,11 @@ export enum HeaderStyle {
|
||||
AppBar = "app_bar",
|
||||
}
|
||||
|
||||
export enum BackgroundStyle {
|
||||
Solid = "solid",
|
||||
Gradient = "gradient",
|
||||
}
|
||||
|
||||
/**
|
||||
* The UrlProperties are used to pass required data to the widget.
|
||||
* Those are different in different rooms, users, devices. They do not configure the behavior of the
|
||||
@@ -145,6 +150,10 @@ export interface UrlProperties {
|
||||
* can be "light", "dark", "light-high-contrast" or "dark-high-contrast".
|
||||
*/
|
||||
theme: string | null;
|
||||
/**
|
||||
* The visual style of the page background.
|
||||
*/
|
||||
background: BackgroundStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,6 +461,9 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => {
|
||||
fonts: parser.getAllParams("font"),
|
||||
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
||||
theme: parser.getParam("theme"),
|
||||
background:
|
||||
parser.getEnumParam("background", BackgroundStyle) ??
|
||||
BackgroundStyle.Gradient,
|
||||
viaServers: !isWidget ? parser.getParam("viaServers") : null,
|
||||
homeserver: !isWidget ? parser.getParam("homeserver") : null,
|
||||
posthogApiHost: parser.getParam("posthogApiHost"),
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
exports[`AppBar > renders 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="bar"
|
||||
class="_bar_221541"
|
||||
>
|
||||
<header>
|
||||
<button
|
||||
aria-labelledby="_r_0_"
|
||||
class="_icon-button_1215g_8 primaryButton"
|
||||
class="_icon-button_1215g_8 _primaryButton_221541"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 24px;"
|
||||
@@ -33,7 +33,7 @@ exports[`AppBar > renders 1`] = `
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="secondaryButton"
|
||||
class="_secondaryButton_221541"
|
||||
/>
|
||||
</header>
|
||||
</div>
|
||||
@@ -46,12 +46,12 @@ exports[`AppBar > renders 1`] = `
|
||||
exports[`AppBar > renders with title and subtitle 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="bar"
|
||||
class="_bar_221541"
|
||||
>
|
||||
<header>
|
||||
<button
|
||||
aria-labelledby="_r_6_"
|
||||
class="_icon-button_1215g_8 primaryButton"
|
||||
class="_icon-button_1215g_8 _primaryButton_221541"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 24px;"
|
||||
@@ -76,17 +76,17 @@ exports[`AppBar > renders with title and subtitle 1`] = `
|
||||
</div>
|
||||
</button>
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-body-lg-medium_6v6n8_79 title"
|
||||
class="_typography_6v6n8_153 _font-body-lg-medium_6v6n8_79 _title_221541"
|
||||
>
|
||||
Title
|
||||
</h1>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69 subtitle"
|
||||
class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69 _subtitle_221541"
|
||||
>
|
||||
Subtitle
|
||||
</span>
|
||||
<div
|
||||
class="secondaryButton"
|
||||
class="_secondaryButton_221541"
|
||||
/>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`the content is rendered when the modal is open 1`] = `
|
||||
<div
|
||||
aria-labelledby="radix-_r_4_"
|
||||
class="overlay animate modal dialog _glass_sepwu_8"
|
||||
class="_overlay_2f5303 _animate_2f5303 _modal_dbeffe _dialog_dbeffe _glass_sepwu_8"
|
||||
data-state="open"
|
||||
id="radix-_r_3_"
|
||||
role="dialog"
|
||||
@@ -11,10 +11,10 @@ exports[`the content is rendered when the modal is open 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_dbeffe"
|
||||
>
|
||||
<div
|
||||
class="header"
|
||||
class="_header_dbeffe"
|
||||
>
|
||||
<h2
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
@@ -24,7 +24,7 @@ exports[`the content is rendered when the modal is open 1`] = `
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class="body"
|
||||
class="_body_dbeffe"
|
||||
>
|
||||
<p>
|
||||
This is the content.
|
||||
@@ -37,7 +37,7 @@ exports[`the content is rendered when the modal is open 1`] = `
|
||||
exports[`the modal renders as a drawer in mobile viewports 1`] = `
|
||||
<div
|
||||
aria-labelledby="radix-_r_a_"
|
||||
class="overlay modal drawer"
|
||||
class="_overlay_2f5303 _modal_dbeffe _drawer_dbeffe"
|
||||
data-state="open"
|
||||
data-vaul-animate="true"
|
||||
data-vaul-custom-container="false"
|
||||
@@ -51,13 +51,13 @@ exports[`the modal renders as a drawer in mobile viewports 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_dbeffe"
|
||||
>
|
||||
<div
|
||||
class="header"
|
||||
class="_header_dbeffe"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
class="_handle_dbeffe"
|
||||
/>
|
||||
<h2
|
||||
id="radix-_r_a_"
|
||||
@@ -67,7 +67,7 @@ exports[`the modal renders as a drawer in mobile viewports 1`] = `
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class="body"
|
||||
class="_body_dbeffe"
|
||||
>
|
||||
<p>
|
||||
This is the content.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`QrCode > renders 1`] = `
|
||||
<div
|
||||
class="qrCode bar"
|
||||
class="_qrCode_458264 bar"
|
||||
>
|
||||
<img
|
||||
alt="QR Code"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`Toast > renders 1`] = `
|
||||
<button
|
||||
aria-labelledby="radix-_r_4_"
|
||||
class="overlay animate toast"
|
||||
class="_overlay_2f5303 _animate_2f5303 _toast_15a045"
|
||||
data-state="open"
|
||||
id="radix-_r_3_"
|
||||
role="dialog"
|
||||
|
||||
@@ -5,10 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
.endCall > svg {
|
||||
color: var(--stopgap-color-on-solid-accent);
|
||||
}
|
||||
|
||||
.rotate > svg {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@@ -135,21 +135,12 @@ interface EndCallButtonProps extends ComponentPropsWithoutRef<"button"> {
|
||||
size?: "md" | "lg";
|
||||
}
|
||||
|
||||
export const EndCallButton: FC<EndCallButtonProps> = ({
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
export const EndCallButton: FC<EndCallButtonProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tooltip label={t("hangup_button_label")}>
|
||||
<CpdButton
|
||||
className={classNames(className, styles.endCall)}
|
||||
iconOnly
|
||||
Icon={EndCallIcon}
|
||||
destructive
|
||||
{...props}
|
||||
/>
|
||||
<CpdButton iconOnly Icon={EndCallIcon} destructive {...props} />
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -140,7 +140,7 @@ exports[`Can raise hand 1`] = `
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-labelledby="_r_1j_"
|
||||
class="_button_1nw83_8 raisedButton _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
class="_button_1nw83_8 _raisedButton_fb25ab _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
|
||||
@@ -19,11 +19,6 @@ Please see LICENSE in the repository root for full details.
|
||||
padding-right: calc(env(safe-area-inset-right) + var(--cpd-space-6x));
|
||||
padding-block: var(--cpd-space-10x)
|
||||
calc(env(safe-area-inset-bottom) + var(--cpd-space-10x));
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
var(--cpd-color-bg-canvas-default) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.footer.hidden {
|
||||
|
||||
@@ -112,11 +112,17 @@ export interface FooterState {
|
||||
}
|
||||
|
||||
export interface FooterProps {
|
||||
className?: string;
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
children?: JSX.Element | JSX.Element[] | false;
|
||||
vm: ViewModel<FooterSnapshot>;
|
||||
}
|
||||
export const CallFooter: FC<FooterProps> = ({ ref, children, vm }) => {
|
||||
export const CallFooter: FC<FooterProps> = ({
|
||||
className,
|
||||
ref,
|
||||
children,
|
||||
vm,
|
||||
}) => {
|
||||
const asOverlay = useBehavior(vm.asOverlay$);
|
||||
const showFooter = useBehavior(vm.showFooter$);
|
||||
const hideControls = useBehavior(vm.hideControls$);
|
||||
@@ -292,7 +298,7 @@ export const CallFooter: FC<FooterProps> = ({ ref, children, vm }) => {
|
||||
<div
|
||||
ref={ref}
|
||||
data-testid="footer-container"
|
||||
className={classNames(styles.footer, {
|
||||
className={classNames(className, styles.footer, {
|
||||
[styles.overlay]: asOverlay,
|
||||
[styles.hidden]: !showFooter,
|
||||
})}
|
||||
|
||||
@@ -180,12 +180,12 @@ describe("MediaMuteAndSwitchButton", () => {
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Microphone" }));
|
||||
screen.getByRole("menuitem", { name: "Microphone 1" });
|
||||
screen.getByRole("menuitem", { name: "Microphone 2" });
|
||||
screen.getByRole("menuitemradio", { name: "Microphone 1" });
|
||||
screen.getByRole("menuitemradio", { name: "Microphone 2" });
|
||||
await user.keyboard("[Escape]");
|
||||
await user.click(screen.getByRole("button", { name: "Camera" }));
|
||||
screen.getByRole("menuitem", { name: "Camera 1" });
|
||||
screen.getByRole("menuitem", { name: "Camera 2" });
|
||||
screen.getByRole("menuitemradio", { name: "Camera 1" });
|
||||
screen.getByRole("menuitemradio", { name: "Camera 2" });
|
||||
});
|
||||
|
||||
test("calls select callback on menu click", async () => {
|
||||
@@ -206,7 +206,9 @@ describe("MediaMuteAndSwitchButton", () => {
|
||||
);
|
||||
|
||||
await user.click(getByRole("button", { name: "Microphone" }));
|
||||
await user.click(screen.getByRole("menuitem", { name: "Microphone 2" }));
|
||||
await user.click(
|
||||
screen.getByRole("menuitemradio", { name: "Microphone 2" }),
|
||||
);
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith("mic2");
|
||||
});
|
||||
@@ -228,7 +230,9 @@ describe("MediaMuteAndSwitchButton", () => {
|
||||
);
|
||||
|
||||
await user.click(getByRole("button", { name: "Microphone" }));
|
||||
await user.click(screen.getByRole("menuitem", { name: "Microphone 1" }));
|
||||
await user.click(
|
||||
screen.getByRole("menuitemradio", { name: "Microphone 1" }),
|
||||
);
|
||||
|
||||
expect(onSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -264,18 +268,24 @@ describe("MediaMuteAndSwitchButton", () => {
|
||||
const { getByRole } = renderComponent(<Wrapper />);
|
||||
|
||||
await user.click(getByRole("button", { name: "Microphone" }));
|
||||
await user.click(screen.getByRole("menuitem", { name: "Microphone 2" }));
|
||||
await user.click(
|
||||
screen.getByRole("menuitemradio", { name: "Microphone 2" }),
|
||||
);
|
||||
|
||||
expect(onSelectPressed).toHaveBeenCalled();
|
||||
expect(onOptionUpdated).not.toHaveBeenCalled();
|
||||
// After clicking, plannedSelection="mic2" but selectedOption is still "mic1",
|
||||
// so a spinner should appear on the mic2 item
|
||||
const mic2Item = screen.getByRole("menuitem", { name: "Microphone 2" });
|
||||
expect(mic2Item.querySelector(".rotate")).toBeTruthy();
|
||||
// so mic2 should be in an activating state
|
||||
screen.getByRole("menuitemradio", {
|
||||
name: "Microphone 2 Activating…",
|
||||
checked: false,
|
||||
});
|
||||
|
||||
// The currently-selected mic1 item should not have a spinner
|
||||
const mic1Item = screen.getByRole("menuitem", { name: "Microphone 1" });
|
||||
expect(mic1Item.querySelector(".rotate")).toBeNull();
|
||||
// The currently-selected mic1 item should not be activating
|
||||
screen.getByRole("menuitemradio", {
|
||||
name: "Microphone 1",
|
||||
checked: true,
|
||||
});
|
||||
await act(async () => {
|
||||
// resolve the promise that acutally updates the select option.
|
||||
resolve();
|
||||
@@ -284,7 +294,7 @@ describe("MediaMuteAndSwitchButton", () => {
|
||||
|
||||
expect(onOptionUpdated).toHaveBeenCalled();
|
||||
// Spinner should now be gone since the selection has caught up
|
||||
const mic2ItemAfter = screen.getByRole("menuitem", {
|
||||
const mic2ItemAfter = screen.getByRole("menuitemradio", {
|
||||
name: "Microphone 2",
|
||||
});
|
||||
expect(mic2ItemAfter.querySelector(".rotate")).toBeNull();
|
||||
@@ -336,11 +346,15 @@ describe("MediaMuteAndSwitchButton", () => {
|
||||
await user.click(getByRole("button", { name: "Microphone" }));
|
||||
|
||||
// The selected item (mic2) renders both an IconOptions SVG and a CheckIcon SVG
|
||||
const mic1Item = screen.getByRole("menuitem", { name: "Microphone 2" });
|
||||
const mic1Item = screen.getByRole("menuitemradio", {
|
||||
name: "Microphone 2",
|
||||
});
|
||||
expect(mic1Item.querySelectorAll("svg").length).toBe(2);
|
||||
|
||||
// The unselected item (mic1) only renders its IconOptions SVG
|
||||
const mic2Item = screen.getByRole("menuitem", { name: "Microphone 1" });
|
||||
const mic2Item = screen.getByRole("menuitemradio", {
|
||||
name: "Microphone 1",
|
||||
});
|
||||
expect(mic2Item.querySelectorAll("svg").length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -191,6 +191,7 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
|
||||
width={24}
|
||||
height={24}
|
||||
className={styles.itemIcon}
|
||||
aria-hidden
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -201,10 +202,23 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
|
||||
onSelect?.(id);
|
||||
}}
|
||||
key={id}
|
||||
role="menuitemradio"
|
||||
aria-checked={selectedOption === id}
|
||||
>
|
||||
{selectedOption === id && <CheckIcon width={24} height={24} />}
|
||||
{selectedOption === id && (
|
||||
<CheckIcon
|
||||
width={24}
|
||||
height={24}
|
||||
aria-hidden // A label would be redundant to aria-checked above
|
||||
/>
|
||||
)}
|
||||
{selectedOption !== id && plannedSelection === id && (
|
||||
<SpinnerIcon width={24} height={24} className={styles.rotate} />
|
||||
<SpinnerIcon
|
||||
width={24}
|
||||
height={24}
|
||||
className={styles.rotate}
|
||||
aria-label={t("settings.devices.activating")}
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`MediaMuteAndSwitchButton > renders 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_e649de"
|
||||
>
|
||||
<button
|
||||
aria-busy="false"
|
||||
@@ -35,7 +35,7 @@ exports[`MediaMuteAndSwitchButton > renders 1`] = `
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Microphone"
|
||||
class="_button_1nw83_8 menuButton _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
class="_button_1nw83_8 _menuButton_e649de _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
data-kind="tertiary"
|
||||
data-size="lg"
|
||||
data-state="closed"
|
||||
|
||||
|
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 938 B |
@@ -1,48 +0,0 @@
|
||||
<svg width="1440" height="500" viewBox="0 0 1440 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_4162_80135)">
|
||||
<circle cx="720" cy="1620" r="1500" fill="url(#paint0_linear_4162_80135)"/>
|
||||
<circle cx="720" cy="1620" r="1498.92" stroke="white" stroke-opacity="0.5" stroke-width="2.16028" style="mix-blend-mode:overlay"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_f_4162_80135)">
|
||||
<circle cx="720" cy="1550.86" r="1272.86" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_4162_80135" x="-900" y="0" width="3240" height="3240" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="60" result="effect1_foregroundBlur_4162_80135"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_4162_80135" x="-672.863" y="158" width="2785.73" height="2785.73" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="60" result="effect1_foregroundBlur_4162_80135"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_4162_80135" x1="549.5" y1="120" x2="549.5" y2="505.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#062993"/>
|
||||
<stop offset="0.040404" stop-color="#02389D"/>
|
||||
<stop offset="0.0808081" stop-color="#0045A6"/>
|
||||
<stop offset="0.121212" stop-color="#0051AD"/>
|
||||
<stop offset="0.161616" stop-color="#005DB4"/>
|
||||
<stop offset="0.20202" stop-color="#0069BA"/>
|
||||
<stop offset="0.242424" stop-color="#0075BB"/>
|
||||
<stop offset="0.282828" stop-color="#0081BB"/>
|
||||
<stop offset="0.323232" stop-color="#008CB9"/>
|
||||
<stop offset="0.363636" stop-color="#0098B7"/>
|
||||
<stop offset="0.40404" stop-color="#00A3B3"/>
|
||||
<stop offset="0.444444" stop-color="#00AEAD"/>
|
||||
<stop offset="0.484848" stop-color="#00B8A4"/>
|
||||
<stop offset="0.525253" stop-color="#00C2A0"/>
|
||||
<stop offset="0.565657" stop-color="#00CC99"/>
|
||||
<stop offset="0.606061" stop-color="#3AD396"/>
|
||||
<stop offset="0.646465" stop-color="#5DD898"/>
|
||||
<stop offset="0.686869" stop-color="#79DD99"/>
|
||||
<stop offset="0.727273" stop-color="#92E29B"/>
|
||||
<stop offset="0.767677" stop-color="#A8E69F"/>
|
||||
<stop offset="0.808081" stop-color="#BBEAA5"/>
|
||||
<stop offset="0.848485" stop-color="#CDEEAE"/>
|
||||
<stop offset="0.888889" stop-color="#DCF2B9"/>
|
||||
<stop offset="0.929293" stop-color="#EAF6C7"/>
|
||||
<stop offset="0.969697" stop-color="#F5FBD5"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
86
src/graphics/mobile-gradient.svg
Normal file
86
src/graphics/mobile-gradient.svg
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="1200"
|
||||
height="285"
|
||||
viewBox="0 0 1200 285"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g
|
||||
filter="url(#filter0_f_3970_9366)"
|
||||
id="g2"
|
||||
transform="translate(422.00201)">
|
||||
<path
|
||||
d="m -164.342,495.134 c 188.2881,-188.288 494.684,-190 684.684,0"
|
||||
stroke="url(#paint0_linear_3970_9366)"
|
||||
stroke-width="235.517"
|
||||
stroke-linecap="round"
|
||||
id="path1"
|
||||
style="stroke:url(#paint0_linear_3970_9366)" />
|
||||
<path
|
||||
d="m -164.342,495.134 c 188.2881,-188.288 494.684,-190 684.684,0"
|
||||
stroke="url(#paint1_linear_3970_9366)"
|
||||
style="mix-blend-mode:overlay;stroke:url(#paint1_linear_3970_9366)"
|
||||
stroke-width="235.517"
|
||||
stroke-linecap="round"
|
||||
id="path2" />
|
||||
</g>
|
||||
<defs
|
||||
id="defs5">
|
||||
<filter
|
||||
id="filter0_f_3970_9366"
|
||||
x="-517.617"
|
||||
y="-0.00012207"
|
||||
width="1391.23"
|
||||
height="848.409"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood
|
||||
flood-opacity="0"
|
||||
result="BackgroundImageFix"
|
||||
id="feFlood2" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
id="feBlend2" />
|
||||
<feGaussianBlur
|
||||
stdDeviation="117.758"
|
||||
result="effect1_foregroundBlur_3970_9366"
|
||||
id="feGaussianBlur2" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_3970_9366"
|
||||
x1="349.17099"
|
||||
y1="323.96301"
|
||||
x2="6.82898"
|
||||
y2="666.30499"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#0D5CBD"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset="0.730863"
|
||||
stop-color="#0DBDA8"
|
||||
id="stop3" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_3970_9366"
|
||||
x1="349.17099"
|
||||
y1="323.96301"
|
||||
x2="6.82898"
|
||||
y2="666.30499"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#0D5CBD"
|
||||
id="stop4" />
|
||||
<stop
|
||||
offset="0.730863"
|
||||
stop-color="#0DBDA8"
|
||||
id="stop5" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -28,12 +28,6 @@ layer(compound);
|
||||
--font-size-title: calc(24px * var(--font-scale));
|
||||
--font-size-headline: calc(32px * var(--font-scale));
|
||||
|
||||
/* These colors are needed during the transitionary period between the old and
|
||||
new Compound design systems, but should be removed ASAP */
|
||||
--stopgap-color-on-solid-accent: var(--cpd-color-bg-canvas-default);
|
||||
--stopgap-background-85: rgba(255, 255, 255, 0.85);
|
||||
--stopgap-bgColor3: #444;
|
||||
|
||||
--cpd-color-border-accent: var(--cpd-color-green-800);
|
||||
/* The distance to inset non-full-width content from the edge of the window
|
||||
along the inline axis. This ramps up from 16px for typical mobile windows, to
|
||||
@@ -55,7 +49,6 @@ layer(compound);
|
||||
--small-drop-shadow: 0px 1.2px 2.4px 0px rgba(0, 0, 0, 0.15);
|
||||
--big-drop-shadow: 0px 0px 24px 0px #1b1d221a;
|
||||
--subtle-drop-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
||||
--background-gradient: url("graphics/backgroundGradient.svg");
|
||||
|
||||
--call-view-overlay-layer: 1;
|
||||
--call-view-header-footer-layer: 2;
|
||||
@@ -74,9 +67,6 @@ layer(compound);
|
||||
|
||||
body {
|
||||
background-color: var(--cpd-color-bg-canvas-default);
|
||||
background-size: calc(max(1440px, 100vw)) calc(max(800px, 100vh));
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
color: var(--cpd-color-text-primary);
|
||||
color-scheme: dark;
|
||||
margin: 0;
|
||||
@@ -85,6 +75,24 @@ body {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@media (min-height: 330px) {
|
||||
body[data-background="gradient"]::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image: url("graphics/mobile-gradient.svg");
|
||||
background-size: auto;
|
||||
background-position: bottom;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body[data-background="gradient"][data-platform="desktop"]::before {
|
||||
background-image: url("graphics/desktop-gradient.svg");
|
||||
background-size: calc(max(1440px, 100vw)) calc(max(800px, 100vh));
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* This prohibits the view to scroll for pages smaller than 122px in width
|
||||
we use this for mobile pip webviews */
|
||||
.no-scroll-body {
|
||||
|
||||
@@ -173,14 +173,6 @@ Please see LICENSE in the repository root for full details.
|
||||
border-color: var(--cpd-color-border-disabled);
|
||||
}
|
||||
|
||||
.checkbox svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkbox svg * {
|
||||
stroke: var(--stopgap-color-on-solid-accent);
|
||||
}
|
||||
|
||||
.checkboxField input[type="checkbox"]:checked + label > .checkbox {
|
||||
background: var(--cpd-color-text-action-accent);
|
||||
border-color: var(--cpd-color-text-action-accent);
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
exports[`RaisedHandIndicator > renders a smaller indicator when miniature is specified 1`] = `
|
||||
<div
|
||||
class="reactionIndicatorWidget"
|
||||
class="_reactionIndicatorWidget_abd277"
|
||||
>
|
||||
<div
|
||||
class="reaction"
|
||||
class="_reaction_abd277"
|
||||
>
|
||||
<span
|
||||
aria-label="Reaction"
|
||||
@@ -22,10 +22,10 @@ exports[`RaisedHandIndicator > renders a smaller indicator when miniature is spe
|
||||
|
||||
exports[`RaisedHandIndicator > renders an indicator when a hand has been raised 1`] = `
|
||||
<div
|
||||
class="reactionIndicatorWidget reactionIndicatorWidgetLarge"
|
||||
class="_reactionIndicatorWidget_abd277 _reactionIndicatorWidgetLarge_abd277"
|
||||
>
|
||||
<div
|
||||
class="reaction reactionLarge"
|
||||
class="_reaction_abd277 _reactionLarge_abd277"
|
||||
>
|
||||
<span
|
||||
aria-label="Reaction"
|
||||
@@ -42,10 +42,10 @@ exports[`RaisedHandIndicator > renders an indicator when a hand has been raised
|
||||
|
||||
exports[`RaisedHandIndicator > renders an indicator when a hand has been raised with the expected time 1`] = `
|
||||
<div
|
||||
class="reactionIndicatorWidget reactionIndicatorWidgetLarge"
|
||||
class="_reactionIndicatorWidget_abd277 _reactionIndicatorWidgetLarge_abd277"
|
||||
>
|
||||
<div
|
||||
class="reaction reactionLarge"
|
||||
class="_reaction_abd277 _reactionLarge_abd277"
|
||||
>
|
||||
<span
|
||||
aria-label="Reaction"
|
||||
|
||||
@@ -14,6 +14,23 @@ Please see LICENSE in the repository root for full details.
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Normally the footer uses a transparent background to allow our expressive
|
||||
page gradients to shine through. However, we sometimes need to visually separate
|
||||
it from the content underneath. If the call layout is overflowing, or if the
|
||||
spotlight tile is maximised and displaying video, apply a gradient background. */
|
||||
.overflowing > .footer,
|
||||
.fixedGrid:has(
|
||||
> .tile[data-maximised="true"]
|
||||
.spotlightItem[data-background="transparent"][data-video-enabled="true"][aria-hidden="false"]
|
||||
)
|
||||
~ .footer {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
var(--cpd-color-bg-canvas-default) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
flex-shrink: 0;
|
||||
@@ -82,19 +99,18 @@ Please see LICENSE in the repository root for full details.
|
||||
/* Disable pointer events so the overlay doesn't block interaction with
|
||||
elements behind it */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fixedGrid > :not(:first-child),
|
||||
.scrollingGrid > :not(:first-child) {
|
||||
pointer-events: initial;
|
||||
> :not(:first-child) {
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.tile {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tile {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
}
|
||||
|
||||
.tile.maximised {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@@ -258,6 +258,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
const reconnecting = useBehavior(vm.reconnecting$);
|
||||
const layout = useBehavior(vm.layout$);
|
||||
const edgeToEdge = useBehavior(vm.edgeToEdge$);
|
||||
const overflowing = useBehavior(vm.overflowing$);
|
||||
const showNameTags = useBehavior(vm.showNameTags$);
|
||||
const showHeader = useBehavior(vm.showHeader$);
|
||||
const settingsOpen = useBehavior(vm.settingsOpen$);
|
||||
@@ -467,6 +468,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
showRingingStatus={showRingingStatus}
|
||||
focusable={!contentObscured}
|
||||
className={classNames(className, styles.tile)}
|
||||
itemClassName={styles.spotlightItem}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
@@ -491,7 +493,9 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
if (layout.type === "pip") {
|
||||
return (
|
||||
<SpotlightTile
|
||||
className={classNames(styles.tile, styles.maximised)}
|
||||
className={styles.tile}
|
||||
itemClassName={styles.spotlightItem}
|
||||
data-maximised
|
||||
vm={layout.spotlight}
|
||||
expanded
|
||||
onToggleExpanded={null}
|
||||
@@ -583,7 +587,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
|
||||
// Only hide the settings button if we have an AppBar header and we are showing the header
|
||||
const footer = footerVm !== null && (
|
||||
<CallFooter ref={footerRef} vm={footerVm} />
|
||||
<CallFooter className={styles.footer} ref={footerRef} vm={footerVm} />
|
||||
);
|
||||
const allConnections = useBehavior(vm.allConnections$);
|
||||
|
||||
@@ -592,7 +596,9 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
// and the footer is also viewable by moving focus into it, so this is fine.
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className={styles.inRoom}
|
||||
className={classNames(styles.inRoom, {
|
||||
[styles.overflowing]: overflowing,
|
||||
})}
|
||||
ref={containerRef}
|
||||
onPointerUp={onViewPointerUp}
|
||||
onPointerMove={onPointerMove}
|
||||
|
||||
@@ -145,8 +145,7 @@ describe("LobbyView", () => {
|
||||
.querySelector("path")!
|
||||
.getAttribute("d");
|
||||
const primaryButtonSvgPath = container
|
||||
.querySelector(".primaryButton")
|
||||
?.querySelector("path")
|
||||
.querySelector("path")
|
||||
?.getAttribute("d");
|
||||
expect(primaryButtonSvgPath).toBe(expectedSvgPath);
|
||||
expect(container).toMatchSnapshot();
|
||||
@@ -169,8 +168,7 @@ describe("LobbyView", () => {
|
||||
.querySelector("path")!
|
||||
.getAttribute("d");
|
||||
const primaryButtonSvgPath = container
|
||||
.querySelector(".primaryButton")
|
||||
?.querySelector("path")
|
||||
.querySelector("path")
|
||||
?.getAttribute("d");
|
||||
expect(primaryButtonSvgPath).toBe(expectedSvgPath);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
exports[`ConnectionLostError: Action handling should reset error state 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -95,20 +95,20 @@ exports[`ConnectionLostError: Action handling should reset error state 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -161,17 +161,17 @@ exports[`ConnectionLostError: Action handling should reset error state 1`] = `
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'internal' error correctly 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -253,20 +253,20 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'internal' er
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -318,17 +318,17 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'internal' er
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'notAllowed' error correctly 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -410,20 +410,20 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'notAllowed'
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -475,17 +475,17 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'notAllowed'
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'serverUnreachable' error correctly 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -567,20 +567,20 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'serverUnreac
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -632,17 +632,17 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'serverUnreac
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'serviceNotFound' error correctly 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -724,20 +724,20 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'serviceNotFo
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -789,17 +789,17 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'serviceNotFo
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'timeout' error correctly 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -881,20 +881,20 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'timeout' err
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -946,17 +946,17 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'timeout' err
|
||||
exports[`LiveKit ConnectionError variants > should link to troubleshoot guide when timeout error 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -1038,20 +1038,20 @@ exports[`LiveKit ConnectionError variants > should link to troubleshoot guide wh
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -1103,17 +1103,17 @@ exports[`LiveKit ConnectionError variants > should link to troubleshoot guide wh
|
||||
exports[`should have a close button in widget mode 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -1195,20 +1195,20 @@ exports[`should have a close button in widget mode 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -1257,17 +1257,17 @@ exports[`should have a close button in widget mode 1`] = `
|
||||
exports[`should render the error page with link back to home 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -1349,20 +1349,20 @@ exports[`should render the error page with link back to home 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -1411,17 +1411,17 @@ exports[`should render the error page with link back to home 1`] = `
|
||||
exports[`should report correct error for 'Call is not supported' 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -1503,20 +1503,20 @@ exports[`should report correct error for 'Call is not supported' 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -1565,17 +1565,17 @@ exports[`should report correct error for 'Call is not supported' 1`] = `
|
||||
exports[`should report correct error for 'Connection lost' 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -1657,20 +1657,20 @@ exports[`should report correct error for 'Connection lost' 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -1723,17 +1723,17 @@ exports[`should report correct error for 'Connection lost' 1`] = `
|
||||
exports[`should report correct error for 'Homeserver does not support Matrix 2.…' 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -1815,20 +1815,20 @@ exports[`should report correct error for 'Homeserver does not support Matrix 2.
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -1877,17 +1877,17 @@ exports[`should report correct error for 'Homeserver does not support Matrix 2.
|
||||
exports[`should report correct error for 'Incompatible browser' 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -1969,20 +1969,20 @@ exports[`should report correct error for 'Incompatible browser' 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -2026,17 +2026,17 @@ exports[`should report correct error for 'Incompatible browser' 1`] = `
|
||||
exports[`should report correct error for 'Insufficient capacity' 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="page"
|
||||
class="_page_4be5c0"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<a
|
||||
aria-label="Element Call Home"
|
||||
class="headerLogo"
|
||||
class="_headerLogo_e4b327"
|
||||
data-discover="true"
|
||||
href="/"
|
||||
>
|
||||
@@ -2118,20 +2118,20 @@ exports[`should report correct error for 'Insufficient capacity' 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_4be5c0"
|
||||
>
|
||||
<div
|
||||
class="error"
|
||||
class="_error_a69dc5"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_a69dc5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
|
||||
@@ -3,28 +3,28 @@
|
||||
exports[`InCallView > rendering > renders 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="inRoom"
|
||||
class="_inRoom_4e7ff8"
|
||||
>
|
||||
<header
|
||||
class="header header"
|
||||
class="_header_e4b327 _header_4e7ff8"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<div
|
||||
class="roomHeaderInfo"
|
||||
class="_roomHeaderInfo_e4b327"
|
||||
data-size="lg"
|
||||
>
|
||||
<span
|
||||
aria-label=""
|
||||
class="_avatar_va14e_8 roomAvatar _avatar-imageless_va14e_55"
|
||||
class="_avatar_va14e_8 _roomAvatar_e4b327 _avatar-imageless_va14e_55"
|
||||
data-color="1"
|
||||
data-type="round"
|
||||
role="img"
|
||||
style="--cpd-avatar-size: 56px;"
|
||||
/>
|
||||
<div
|
||||
class="nameLine"
|
||||
class="_nameLine_e4b327"
|
||||
>
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
@@ -35,7 +35,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-labelledby="_r_0_"
|
||||
class="lock"
|
||||
class="_lock_edc97d"
|
||||
data-encrypted="false"
|
||||
fill="currentColor"
|
||||
height="16"
|
||||
@@ -50,7 +50,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="participantsLine"
|
||||
class="_participantsLine_e4b327"
|
||||
>
|
||||
<svg
|
||||
aria-label="Participants"
|
||||
@@ -80,21 +80,21 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="scrollingGrid grid"
|
||||
class="_scrollingGrid_4e7ff8 _grid_b0d1cd"
|
||||
>
|
||||
<div
|
||||
class="layer"
|
||||
class="_layer_b00c5f"
|
||||
>
|
||||
<div
|
||||
class="container slot"
|
||||
class="_container_b00c5f _slot_b0d1cd"
|
||||
data-id="1"
|
||||
>
|
||||
<div
|
||||
class="slot local slot"
|
||||
class="_slot_b00c5f _local_b00c5f _slot_b0d1cd"
|
||||
data-block-alignment="start"
|
||||
data-id="0"
|
||||
data-inline-alignment="end"
|
||||
@@ -103,22 +103,22 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="fixedGrid grid"
|
||||
class="_fixedGrid_4e7ff8 _grid_b0d1cd"
|
||||
style="inset-block-start: NaNpx;"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="bg animate"
|
||||
class="_bg_2f5303 _animate_2f5303"
|
||||
data-state="closed"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="overlay"
|
||||
class="_overlay_eb6724"
|
||||
data-show="false"
|
||||
>
|
||||
<div
|
||||
class="_big-icon_1ssbv_8 icon"
|
||||
class="_big-icon_1ssbv_8 _icon_eb6724"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
>
|
||||
@@ -157,22 +157,22 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
Back to Speaker Mode
|
||||
</button>
|
||||
<div
|
||||
class="spacer"
|
||||
class="_spacer_eb6724"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="container"
|
||||
class="_container_8084b5"
|
||||
/>
|
||||
<div
|
||||
class="footer"
|
||||
class="_footer_4e7ff8 _footer_20b7b4"
|
||||
data-testid="footer-container"
|
||||
>
|
||||
<div
|
||||
class="settingsLogoContainer"
|
||||
class="_settingsLogoContainer_20b7b4"
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_8_"
|
||||
class="_icon-button_1215g_8 settingsOnlyShowWide"
|
||||
class="_icon-button_1215g_8 _settingsOnlyShowWide_20b7b4"
|
||||
data-kind="secondary"
|
||||
data-testid="settings-bottom-left"
|
||||
role="button"
|
||||
@@ -198,7 +198,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="logo"
|
||||
class="_logo_20b7b4"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -303,11 +303,11 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="buttons"
|
||||
class="_buttons_20b7b4"
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_d_"
|
||||
class="_button_1nw83_8 settingsOnlyShowNarrow _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
class="_button_1nw83_8 _settingsOnlyShowNarrow_20b7b4 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
data-kind="secondary"
|
||||
data-size="lg"
|
||||
data-testid="settings-bottom-center"
|
||||
@@ -382,7 +382,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-labelledby="_r_s_"
|
||||
class="_button_1nw83_8 raiseHand _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
class="_button_1nw83_8 _raiseHand_20b7b4 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
data-kind="secondary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
@@ -405,7 +405,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-labelledby="_r_14_"
|
||||
class="_button_1nw83_8 endCall _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||
class="_button_1nw83_8 _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
data-testid="incall_leave"
|
||||
@@ -428,7 +428,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
</div>
|
||||
<fieldset
|
||||
aria-label="Layout"
|
||||
class="_toggle_13rnk_9 layout"
|
||||
class="_toggle_13rnk_9 _layout_20b7b4"
|
||||
data-size="lg"
|
||||
>
|
||||
<input
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
exports[`LobbyView > renders with AppBar android 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="bar"
|
||||
class="_bar_221541"
|
||||
>
|
||||
<header>
|
||||
<button
|
||||
aria-labelledby="_r_36_"
|
||||
class="_icon-button_1215g_8 primaryButton"
|
||||
class="_icon-button_1215g_8 _primaryButton_221541"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -33,18 +33,18 @@ exports[`LobbyView > renders with AppBar android 1`] = `
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="secondaryButton"
|
||||
class="_secondaryButton_221541"
|
||||
/>
|
||||
</header>
|
||||
</div>
|
||||
<div
|
||||
class="inRoom"
|
||||
class="_inRoom_4e7ff8"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_f9ee84"
|
||||
>
|
||||
<div
|
||||
class="preview"
|
||||
class="_preview_dd2178"
|
||||
>
|
||||
<video
|
||||
disablepictureinpicture=""
|
||||
@@ -52,7 +52,7 @@ exports[`LobbyView > renders with AppBar android 1`] = `
|
||||
tabindex="-1"
|
||||
/>
|
||||
<div
|
||||
class="avatarContainer"
|
||||
class="_avatarContainer_dd2178"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
@@ -68,11 +68,11 @@ exports[`LobbyView > renders with AppBar android 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="buttonBar"
|
||||
class="_buttonBar_dd2178"
|
||||
>
|
||||
<button
|
||||
aria-disabled="true"
|
||||
class="_button_1nw83_8 join wait"
|
||||
class="_button_1nw83_8 _join_f9ee84 _wait_f9ee84"
|
||||
data-kind="primary"
|
||||
data-size="md"
|
||||
data-testid="lobby_joinCall"
|
||||
@@ -94,15 +94,15 @@ exports[`LobbyView > renders with AppBar android 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="footer"
|
||||
class="_footer_20b7b4"
|
||||
data-testid="footer-container"
|
||||
>
|
||||
<div
|
||||
class="settingsLogoContainer"
|
||||
class="_settingsLogoContainer_20b7b4"
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_3c_"
|
||||
class="_icon-button_1215g_8 settingsOnlyShowWide"
|
||||
class="_icon-button_1215g_8 _settingsOnlyShowWide_20b7b4"
|
||||
data-kind="secondary"
|
||||
data-testid="settings-bottom-left"
|
||||
role="button"
|
||||
@@ -129,11 +129,11 @@ exports[`LobbyView > renders with AppBar android 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="buttons"
|
||||
class="_buttons_20b7b4"
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_3h_"
|
||||
class="_button_1nw83_8 settingsOnlyShowNarrow _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
class="_button_1nw83_8 _settingsOnlyShowNarrow_20b7b4 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
data-kind="secondary"
|
||||
data-size="lg"
|
||||
data-testid="settings-bottom-center"
|
||||
@@ -205,7 +205,7 @@ exports[`LobbyView > renders with AppBar android 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-labelledby="_r_40_"
|
||||
class="_button_1nw83_8 endCall _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||
class="_button_1nw83_8 _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
data-testid="incall_leave"
|
||||
@@ -234,12 +234,12 @@ exports[`LobbyView > renders with AppBar android 1`] = `
|
||||
exports[`LobbyView > renders with AppBar ios 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="bar"
|
||||
class="_bar_221541"
|
||||
>
|
||||
<header>
|
||||
<button
|
||||
aria-labelledby="_r_4a_"
|
||||
class="_icon-button_1215g_8 primaryButton"
|
||||
class="_icon-button_1215g_8 _primaryButton_221541"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -264,18 +264,18 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="secondaryButton"
|
||||
class="_secondaryButton_221541"
|
||||
/>
|
||||
</header>
|
||||
</div>
|
||||
<div
|
||||
class="inRoom"
|
||||
class="_inRoom_4e7ff8"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_f9ee84"
|
||||
>
|
||||
<div
|
||||
class="preview"
|
||||
class="_preview_dd2178"
|
||||
>
|
||||
<video
|
||||
disablepictureinpicture=""
|
||||
@@ -283,7 +283,7 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
|
||||
tabindex="-1"
|
||||
/>
|
||||
<div
|
||||
class="avatarContainer"
|
||||
class="_avatarContainer_dd2178"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
@@ -299,11 +299,11 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="buttonBar"
|
||||
class="_buttonBar_dd2178"
|
||||
>
|
||||
<button
|
||||
aria-disabled="true"
|
||||
class="_button_1nw83_8 join wait"
|
||||
class="_button_1nw83_8 _join_f9ee84 _wait_f9ee84"
|
||||
data-kind="primary"
|
||||
data-size="md"
|
||||
data-testid="lobby_joinCall"
|
||||
@@ -325,15 +325,15 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="footer"
|
||||
class="_footer_20b7b4"
|
||||
data-testid="footer-container"
|
||||
>
|
||||
<div
|
||||
class="settingsLogoContainer"
|
||||
class="_settingsLogoContainer_20b7b4"
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_4g_"
|
||||
class="_icon-button_1215g_8 settingsOnlyShowWide"
|
||||
class="_icon-button_1215g_8 _settingsOnlyShowWide_20b7b4"
|
||||
data-kind="secondary"
|
||||
data-testid="settings-bottom-left"
|
||||
role="button"
|
||||
@@ -360,11 +360,11 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="buttons"
|
||||
class="_buttons_20b7b4"
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_4l_"
|
||||
class="_button_1nw83_8 settingsOnlyShowNarrow _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
class="_button_1nw83_8 _settingsOnlyShowNarrow_20b7b4 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
data-kind="secondary"
|
||||
data-size="lg"
|
||||
data-testid="settings-bottom-center"
|
||||
@@ -436,7 +436,7 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-labelledby="_r_54_"
|
||||
class="_button_1nw83_8 endCall _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||
class="_button_1nw83_8 _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
data-testid="incall_leave"
|
||||
@@ -465,21 +465,21 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
|
||||
exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="inRoom"
|
||||
class="_inRoom_4e7ff8"
|
||||
>
|
||||
<header
|
||||
class="header"
|
||||
class="_header_e4b327"
|
||||
>
|
||||
<div
|
||||
class="nav leftNav"
|
||||
class="_nav_e4b327 _leftNav_e4b327"
|
||||
>
|
||||
<div
|
||||
class="roomHeaderInfo"
|
||||
class="_roomHeaderInfo_e4b327"
|
||||
data-size="lg"
|
||||
>
|
||||
<span
|
||||
aria-label="!room:example.org"
|
||||
class="_avatar_va14e_8 roomAvatar _avatar-imageless_va14e_55"
|
||||
class="_avatar_va14e_8 _roomAvatar_e4b327 _avatar-imageless_va14e_55"
|
||||
data-color="3"
|
||||
data-type="round"
|
||||
role="img"
|
||||
@@ -488,7 +488,7 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
T
|
||||
</span>
|
||||
<div
|
||||
class="nameLine"
|
||||
class="_nameLine_e4b327"
|
||||
>
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
@@ -501,7 +501,7 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-labelledby="_r_0_"
|
||||
class="lock"
|
||||
class="_lock_edc97d"
|
||||
data-encrypted="false"
|
||||
fill="currentColor"
|
||||
height="16"
|
||||
@@ -516,7 +516,7 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="participantsLine"
|
||||
class="_participantsLine_e4b327"
|
||||
>
|
||||
<svg
|
||||
aria-label="Participants"
|
||||
@@ -546,14 +546,14 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="nav rightNav"
|
||||
class="_nav_e4b327 _rightNav_e4b327"
|
||||
/>
|
||||
</header>
|
||||
<div
|
||||
class="content"
|
||||
class="_content_f9ee84"
|
||||
>
|
||||
<div
|
||||
class="preview"
|
||||
class="_preview_dd2178"
|
||||
>
|
||||
<video
|
||||
disablepictureinpicture=""
|
||||
@@ -561,7 +561,7 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
tabindex="-1"
|
||||
/>
|
||||
<div
|
||||
class="avatarContainer"
|
||||
class="_avatarContainer_dd2178"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
@@ -577,10 +577,10 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="buttonBar"
|
||||
class="_buttonBar_dd2178"
|
||||
>
|
||||
<button
|
||||
class="_button_1nw83_8 join"
|
||||
class="_button_1nw83_8 _join_f9ee84"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
data-testid="lobby_joinCall"
|
||||
@@ -602,15 +602,15 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="footer"
|
||||
class="_footer_20b7b4"
|
||||
data-testid="footer-container"
|
||||
>
|
||||
<div
|
||||
class="settingsLogoContainer"
|
||||
class="_settingsLogoContainer_20b7b4"
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_6_"
|
||||
class="_icon-button_1215g_8 settingsOnlyShowWide"
|
||||
class="_icon-button_1215g_8 _settingsOnlyShowWide_20b7b4"
|
||||
data-kind="secondary"
|
||||
data-testid="settings-bottom-left"
|
||||
role="button"
|
||||
@@ -636,7 +636,7 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="logo"
|
||||
class="_logo_20b7b4"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -741,11 +741,11 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="buttons"
|
||||
class="_buttons_20b7b4"
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_b_"
|
||||
class="_button_1nw83_8 settingsOnlyShowNarrow _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
class="_button_1nw83_8 _settingsOnlyShowNarrow_20b7b4 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||
data-kind="secondary"
|
||||
data-size="lg"
|
||||
data-testid="settings-bottom-center"
|
||||
@@ -817,7 +817,7 @@ exports[`LobbyView > renders with header and participant count 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-labelledby="_r_q_"
|
||||
class="_button_1nw83_8 endCall _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||
class="_button_1nw83_8 _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
data-testid="incall_leave"
|
||||
|
||||
@@ -18,10 +18,10 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
Device ID: DEVICE123
|
||||
</p>
|
||||
<div
|
||||
class="fieldRow"
|
||||
class="_fieldRow_1bd8c0"
|
||||
>
|
||||
<div
|
||||
class="field inputField"
|
||||
class="_field_1bd8c0 _inputField_1bd8c0"
|
||||
>
|
||||
<input
|
||||
aria-describedby="_r_1_"
|
||||
@@ -38,10 +38,10 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="fieldRow"
|
||||
class="_fieldRow_1bd8c0"
|
||||
>
|
||||
<div
|
||||
class="field checkboxField"
|
||||
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||
>
|
||||
<input
|
||||
aria-describedby="_r_2_"
|
||||
@@ -52,7 +52,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
for="debugTileLayout"
|
||||
>
|
||||
<div
|
||||
class="checkbox"
|
||||
class="_checkbox_1bd8c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@@ -75,10 +75,10 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="fieldRow"
|
||||
class="_fieldRow_1bd8c0"
|
||||
>
|
||||
<div
|
||||
class="field checkboxField"
|
||||
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||
>
|
||||
<input
|
||||
aria-describedby="_r_3_"
|
||||
@@ -89,7 +89,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
for="showConnectionStats"
|
||||
>
|
||||
<div
|
||||
class="checkbox"
|
||||
class="_checkbox_1bd8c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@@ -112,10 +112,10 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="fieldRow"
|
||||
class="_fieldRow_1bd8c0"
|
||||
>
|
||||
<div
|
||||
class="field checkboxField"
|
||||
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||
>
|
||||
<input
|
||||
aria-describedby="_r_4_"
|
||||
@@ -126,7 +126,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
for="muteAllAudio"
|
||||
>
|
||||
<div
|
||||
class="checkbox"
|
||||
class="_checkbox_1bd8c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@@ -150,10 +150,10 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="fieldRow"
|
||||
class="_fieldRow_1bd8c0"
|
||||
>
|
||||
<div
|
||||
class="field checkboxField"
|
||||
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||
>
|
||||
<input
|
||||
aria-describedby="_r_5_"
|
||||
@@ -164,7 +164,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
for="alwaysShowIphoneEarpiece"
|
||||
>
|
||||
<div
|
||||
class="checkbox"
|
||||
class="_checkbox_1bd8c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@@ -187,10 +187,10 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="fieldRow"
|
||||
class="_fieldRow_1bd8c0"
|
||||
>
|
||||
<div
|
||||
class="field checkboxField"
|
||||
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||
>
|
||||
<input
|
||||
aria-describedby="_r_6_"
|
||||
@@ -201,7 +201,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
for="enableLivekitExtendedLogs"
|
||||
>
|
||||
<div
|
||||
class="checkbox"
|
||||
class="_checkbox_1bd8c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@@ -386,7 +386,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
class="livekit_room_box"
|
||||
class="_livekit_room_box_2ddec4"
|
||||
>
|
||||
<h4>
|
||||
LiveKit SFU: wss://local-sfu.example.org
|
||||
@@ -427,7 +427,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
||||
<ul />
|
||||
</div>
|
||||
<div
|
||||
class="livekit_room_box"
|
||||
class="_livekit_room_box_2ddec4"
|
||||
>
|
||||
<h4>
|
||||
LiveKit SFU: wss://remote-sfu.example.org
|
||||
|
||||
@@ -207,6 +207,7 @@ export type WindowMode = "normal" | "narrow" | "flat" | "pip";
|
||||
|
||||
interface LayoutScanState {
|
||||
layout: Layout | null;
|
||||
overflowing: boolean;
|
||||
tiles: TileStore;
|
||||
}
|
||||
|
||||
@@ -359,6 +360,10 @@ export interface CallViewModel {
|
||||
* and header as overlays.
|
||||
*/
|
||||
edgeToEdge$: Behavior<boolean>;
|
||||
/**
|
||||
* Whether the call layout is overflowing the interface (causing it to scroll).
|
||||
*/
|
||||
overflowing$: Behavior<boolean>;
|
||||
|
||||
settingsOpen$: Behavior<boolean>;
|
||||
setSettingsOpen$: Behavior<(open: boolean) => void>;
|
||||
@@ -1468,7 +1473,7 @@ export function createCallViewModel$(
|
||||
|
||||
// There is a cyclical dependency here: the layout algorithms want to know
|
||||
// which tiles are on screen, but to know which tiles are on screen we have to
|
||||
// first render a layout. To deal with this we assume initially that no tiles
|
||||
// first render a layout. To deal with this we assume initially that all tiles
|
||||
// are visible, and loop the data back into the layouts with a Subject.
|
||||
const visibleTiles$ = new Subject<number>();
|
||||
const setVisibleTiles = (value: number): void => visibleTiles$.next(value);
|
||||
@@ -1476,7 +1481,7 @@ export function createCallViewModel$(
|
||||
const layoutInternals$ = scope.behavior<LayoutScanState & { layout: Layout }>(
|
||||
combineLatest([
|
||||
layoutMedia$,
|
||||
visibleTiles$.pipe(startWith(0), distinctUntilChanged()),
|
||||
visibleTiles$.pipe(startWith(Infinity), distinctUntilChanged()),
|
||||
]).pipe(
|
||||
scan<
|
||||
[LayoutMedia, number],
|
||||
@@ -1487,6 +1492,7 @@ export function createCallViewModel$(
|
||||
let layout: Layout;
|
||||
let newTiles: TileStore;
|
||||
let pip: GridTileViewModel | undefined;
|
||||
let overflowing = false;
|
||||
switch (media.type) {
|
||||
case "grid":
|
||||
case "spotlight-landscape":
|
||||
@@ -1498,6 +1504,7 @@ export function createCallViewModel$(
|
||||
setVisibleTiles,
|
||||
prevTiles,
|
||||
);
|
||||
overflowing = newTiles.gridTiles.length > visibleTiles;
|
||||
break;
|
||||
case "spotlight-expanded":
|
||||
[layout, newTiles] = spotlightExpandedLayout(
|
||||
@@ -1532,9 +1539,9 @@ export function createCallViewModel$(
|
||||
tile.setShowOutline(tile === pip);
|
||||
}
|
||||
|
||||
return { layout, tiles: newTiles };
|
||||
return { layout, overflowing, tiles: newTiles };
|
||||
},
|
||||
{ layout: null, tiles: TileStore.empty() },
|
||||
{ layout: null, overflowing: false, tiles: TileStore.empty() },
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1546,6 +1553,10 @@ export function createCallViewModel$(
|
||||
layoutInternals$.pipe(map(({ layout }) => layout)),
|
||||
);
|
||||
|
||||
const overflowing$ = scope.behavior<boolean>(
|
||||
layoutInternals$.pipe(map(({ overflowing }) => overflowing)),
|
||||
);
|
||||
|
||||
/**
|
||||
* The current generation of the tile store, exposed for debugging purposes.
|
||||
*/
|
||||
@@ -1787,6 +1798,7 @@ export function createCallViewModel$(
|
||||
settingsOpen$: settingsOpen$,
|
||||
setSettingsOpen$: setSettingsOpen$,
|
||||
edgeToEdge$,
|
||||
overflowing$,
|
||||
earpieceMode$: earpieceMode$,
|
||||
audioOutputSwitcher$: audioOutputSwitcher$,
|
||||
reconnecting$: localMembership.reconnecting$,
|
||||
|
||||
@@ -26,7 +26,7 @@ export function oneOnOnePortraitLayout(
|
||||
prevTiles: TileStore,
|
||||
): [OneOnOnePortraitLayout, TileStore] {
|
||||
const update = prevTiles.from(media.pip === undefined ? 0 : 1);
|
||||
update.registerSpotlight([media.spotlight], true);
|
||||
update.registerSpotlight([media.spotlight], true, "transparent");
|
||||
if (media.pip !== undefined) update.registerGridTile(media.pip);
|
||||
const tiles = update.build();
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export function pipLayout(
|
||||
update.registerSpotlight(
|
||||
media.spotlight,
|
||||
platform === "desktop" ? false : true,
|
||||
"transparent",
|
||||
);
|
||||
const tiles = update.build();
|
||||
return [
|
||||
|
||||
@@ -23,7 +23,7 @@ export function spotlightExpandedLayout(
|
||||
prevTiles: TileStore,
|
||||
): [SpotlightExpandedLayout, TileStore] {
|
||||
const update = prevTiles.from(1);
|
||||
update.registerSpotlight(media.spotlight, true);
|
||||
update.registerSpotlight(media.spotlight, true, "transparent");
|
||||
if (media.pip !== undefined) update.registerPipTile(media.pip);
|
||||
const tiles = update.build();
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import { type MediaViewModel } from "./media/MediaViewModel";
|
||||
import { type UserMediaViewModel } from "./media/UserMediaViewModel";
|
||||
import { type RingingMediaViewModel } from "./media/RingingMediaViewModel";
|
||||
|
||||
type SpotlightBackground = "solid" | "transparent";
|
||||
|
||||
function debugEntries(entries: GridTileData[]): string[] {
|
||||
return entries.map((e) => e.media.displayName$.value);
|
||||
}
|
||||
@@ -39,12 +41,29 @@ class SpotlightTileData {
|
||||
this.maximised$.next(value);
|
||||
}
|
||||
|
||||
private readonly background$: BehaviorSubject<SpotlightBackground>;
|
||||
public get background(): SpotlightBackground {
|
||||
return this.background$.value;
|
||||
}
|
||||
public set background(value: SpotlightBackground) {
|
||||
this.background$.next(value);
|
||||
}
|
||||
|
||||
public readonly vm: SpotlightTileViewModel;
|
||||
|
||||
public constructor(media: MediaViewModel[], maximised: boolean) {
|
||||
public constructor(
|
||||
media: MediaViewModel[],
|
||||
maximised: boolean,
|
||||
background: SpotlightBackground,
|
||||
) {
|
||||
this.media$ = new BehaviorSubject(media);
|
||||
this.maximised$ = new BehaviorSubject(maximised);
|
||||
this.vm = new SpotlightTileViewModel(this.media$, this.maximised$);
|
||||
this.background$ = new BehaviorSubject(background);
|
||||
this.vm = new SpotlightTileViewModel(
|
||||
this.media$,
|
||||
this.maximised$,
|
||||
this.background$,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +176,11 @@ export class TileStoreBuilder {
|
||||
* Sets the contents of the spotlight tile. If this is never called, there
|
||||
* will be no spotlight tile.
|
||||
*/
|
||||
public registerSpotlight(media: MediaViewModel[], maximised: boolean): void {
|
||||
public registerSpotlight(
|
||||
media: MediaViewModel[],
|
||||
maximised: boolean,
|
||||
background: SpotlightBackground = "solid",
|
||||
): void {
|
||||
if (DEBUG_ENABLED)
|
||||
logger.debug(
|
||||
`[TileStore, ${this.generation}] register spotlight: ${media.map((m) => m.displayName$.value)}`,
|
||||
@@ -169,11 +192,12 @@ export class TileStoreBuilder {
|
||||
|
||||
// Reuse the previous spotlight tile if it exists
|
||||
if (this.prevSpotlight === null) {
|
||||
this.spotlight = new SpotlightTileData(media, maximised);
|
||||
this.spotlight = new SpotlightTileData(media, maximised, background);
|
||||
} else {
|
||||
this.spotlight = this.prevSpotlight;
|
||||
this.spotlight.media = media;
|
||||
this.spotlight.maximised = maximised;
|
||||
this.spotlight.background = background;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export class SpotlightTileViewModel {
|
||||
public constructor(
|
||||
public readonly media$: Behavior<MediaViewModel[]>,
|
||||
public readonly maximised$: Behavior<boolean>,
|
||||
public readonly background$: Behavior<"solid" | "transparent">,
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ Please see LICENSE in the repository root for full details.
|
||||
transform: translate(0);
|
||||
}
|
||||
|
||||
.media[data-video-enabled="false"] video {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.media.mirror video {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
@@ -41,13 +45,64 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
.bg {
|
||||
grid-area: content;
|
||||
background-color: var(--video-tile-background);
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
border-radius: inherit;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.media[data-background="solid"] .bg {
|
||||
background-color: var(--video-tile-background);
|
||||
}
|
||||
|
||||
.waves {
|
||||
transition: opacity ease 0.3s;
|
||||
}
|
||||
|
||||
.waves[data-visible="true"] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.waves[data-visible="false"] {
|
||||
opacity: 0;
|
||||
|
||||
@media not (prefers-reduced-motion) {
|
||||
.wave {
|
||||
transform: translate(-50%, -50%) scale(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wave {
|
||||
border: var(--cpd-border-width-1) solid var(--cpd-color-alpha-gray-300);
|
||||
transition: transform ease 0.2s;
|
||||
}
|
||||
|
||||
.wave,
|
||||
.speakingBorder {
|
||||
border-radius: var(--cpd-radius-pill-effect);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.speakingBorder {
|
||||
background:
|
||||
radial-gradient(#0467dd, #0bc491),
|
||||
linear-gradient(0deg, #0467dd 0%, #0bc491 100%);
|
||||
background-blend-mode: overlay, normal;
|
||||
outline: var(--cpd-border-width-4) solid var(--cpd-color-bg-canvas-default);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: var(--cpd-border-width-2);
|
||||
border-radius: var(--cpd-radius-pill-effect);
|
||||
background: var(--cpd-color-bg-canvas-default);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -70,6 +125,35 @@ unconditionally select the container so we can use cqmin units */
|
||||
inline-size: 50cqmin;
|
||||
block-size: 50cqmin;
|
||||
}
|
||||
|
||||
.waves + .avatar {
|
||||
/* Make the avatar slightly smaller to accommodate sound waves, if present */
|
||||
inline-size: 38cqmin;
|
||||
block-size: 38cqmin;
|
||||
}
|
||||
|
||||
.wave:nth-child(1) {
|
||||
inline-size: calc(38cqmin + var(--cpd-space-5x) + 3 * var(--cpd-space-10x));
|
||||
block-size: calc(38cqmin + var(--cpd-space-5x) + 3 * var(--cpd-space-10x));
|
||||
background: var(--cpd-color-alpha-gray-200);
|
||||
}
|
||||
|
||||
.wave:nth-child(2) {
|
||||
inline-size: calc(38cqmin + var(--cpd-space-5x) + 2 * var(--cpd-space-10x));
|
||||
block-size: calc(38cqmin + var(--cpd-space-5x) + 2 * var(--cpd-space-10x));
|
||||
background: var(--cpd-color-alpha-gray-300);
|
||||
}
|
||||
|
||||
.wave:nth-child(3) {
|
||||
inline-size: calc(38cqmin + var(--cpd-space-5x) + var(--cpd-space-10x));
|
||||
block-size: calc(38cqmin + var(--cpd-space-5x) + var(--cpd-space-10x));
|
||||
background: var(--cpd-color-alpha-gray-400);
|
||||
}
|
||||
|
||||
.speakingBorder {
|
||||
inline-size: calc(38cqmin + var(--cpd-space-3x));
|
||||
block-size: calc(38cqmin + var(--cpd-space-3x));
|
||||
}
|
||||
}
|
||||
|
||||
.avatar > img {
|
||||
|
||||
@@ -125,30 +125,4 @@ describe("MediaView", () => {
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("videoEnabled", () => {
|
||||
test("just video is visible", () => {
|
||||
render(
|
||||
<TooltipProvider>
|
||||
<MediaView {...baseProps} videoEnabled={true} />
|
||||
</TooltipProvider>,
|
||||
);
|
||||
expect(screen.getByTestId("video")).toBeVisible();
|
||||
expect(screen.queryAllByRole("img", { name: "some name" }).length).toBe(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test("just avatar is visible", () => {
|
||||
render(
|
||||
<TooltipProvider>
|
||||
<MediaView {...baseProps} videoEnabled={false} />
|
||||
</TooltipProvider>,
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("img", { name: "@alice:example.com" }),
|
||||
).toBeVisible();
|
||||
expect(screen.getByTestId("video")).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ interface Props extends ComponentProps<typeof animated.div> {
|
||||
video: TrackReferenceOrPlaceholder | undefined;
|
||||
videoFit: "cover" | "contain";
|
||||
mirror: boolean;
|
||||
soundWaves?: boolean;
|
||||
userId: string;
|
||||
videoEnabled: boolean;
|
||||
unencryptedWarning: boolean;
|
||||
@@ -43,6 +44,7 @@ interface Props extends ComponentProps<typeof animated.div> {
|
||||
displayName: string;
|
||||
mxcAvatarUrl: string | undefined;
|
||||
avatarStyle?: "solid" | "translucent";
|
||||
background?: "solid" | "transparent";
|
||||
focusable: boolean;
|
||||
primaryButton?: ReactNode;
|
||||
raisedHandTime?: Date;
|
||||
@@ -65,6 +67,7 @@ export const MediaView: FC<Props> = ({
|
||||
video,
|
||||
videoFit,
|
||||
mirror,
|
||||
soundWaves,
|
||||
userId,
|
||||
videoEnabled,
|
||||
unencryptedWarning,
|
||||
@@ -73,6 +76,7 @@ export const MediaView: FC<Props> = ({
|
||||
displayName,
|
||||
mxcAvatarUrl,
|
||||
avatarStyle = "solid",
|
||||
background = "solid",
|
||||
focusable,
|
||||
primaryButton,
|
||||
status,
|
||||
@@ -90,7 +94,10 @@ export const MediaView: FC<Props> = ({
|
||||
const [handRaiseTimerVisible] = useSetting(showHandRaisedTimer);
|
||||
const [showConnectionStats] = useSetting(showConnectionStatsSetting);
|
||||
|
||||
const avatarSize = Math.round(Math.min(targetWidth, targetHeight) / 2);
|
||||
const avatarSize = Math.round(
|
||||
Math.min(targetWidth, targetHeight) *
|
||||
(soundWaves === undefined ? 0.5 : 0.38),
|
||||
);
|
||||
|
||||
const warnings = unencryptedWarning && (
|
||||
<Tooltip
|
||||
@@ -117,10 +124,20 @@ export const MediaView: FC<Props> = ({
|
||||
style={style}
|
||||
ref={ref}
|
||||
data-testid="videoTile"
|
||||
data-video-enabled={video && videoEnabled}
|
||||
data-video-fit={videoFit}
|
||||
data-background={background}
|
||||
{...props}
|
||||
>
|
||||
<div className={styles.bg}>
|
||||
{soundWaves !== undefined && (
|
||||
<div className={styles.waves} data-visible={soundWaves}>
|
||||
<div className={styles.wave} />
|
||||
<div className={styles.wave} />
|
||||
<div className={styles.wave} />
|
||||
<div className={styles.speakingBorder} />
|
||||
</div>
|
||||
)}
|
||||
<Avatar
|
||||
id={userId}
|
||||
name={displayName}
|
||||
@@ -136,7 +153,6 @@ export const MediaView: FC<Props> = ({
|
||||
// There's no reason for this to be focusable
|
||||
tabIndex={-1}
|
||||
disablePictureInPicture
|
||||
style={{ display: video && videoEnabled ? "block" : "none" }}
|
||||
data-testid="video"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -23,7 +23,7 @@ Please see LICENSE in the repository root for full details.
|
||||
scroll-behavior: smooth; */
|
||||
}
|
||||
|
||||
.tile.maximised .contents {
|
||||
.tile[data-maximised="true"] .contents {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ Please see LICENSE in the repository root for full details.
|
||||
--media-view-fg-inset: 10px;
|
||||
}
|
||||
|
||||
.maximised .item {
|
||||
.tile[data-maximised="true"] .item {
|
||||
/* Ensure that foreground elements lie within the safe area */
|
||||
--media-view-fg-inset: calc(var(--call-view-safe-area-inset-top, 0px) + 10px)
|
||||
calc(env(safe-area-inset-right) + 10px)
|
||||
@@ -191,7 +191,7 @@ Please see LICENSE in the repository root for full details.
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.maximised .indicators {
|
||||
.tile[data-maximised="true"] .indicators {
|
||||
inset-block-end: calc(-1 * var(--cpd-space-4x) - 2px);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,13 @@ test("SpotlightTile is accessible", async () => {
|
||||
const toggleExpanded = vi.fn();
|
||||
const { container } = render(
|
||||
<SpotlightTile
|
||||
vm={new SpotlightTileViewModel(constant([vm1, vm2]), constant(false))}
|
||||
vm={
|
||||
new SpotlightTileViewModel(
|
||||
constant([vm1, vm2]),
|
||||
constant(false),
|
||||
constant("solid"),
|
||||
)
|
||||
}
|
||||
targetWidth={300}
|
||||
targetHeight={200}
|
||||
expanded={false}
|
||||
@@ -101,7 +107,13 @@ test("Screen share volume UI is shown when screen share has audio", async () =>
|
||||
const { container } = render(
|
||||
<TooltipProvider>
|
||||
<SpotlightTile
|
||||
vm={new SpotlightTileViewModel(constant([vm]), constant(false))}
|
||||
vm={
|
||||
new SpotlightTileViewModel(
|
||||
constant([vm]),
|
||||
constant(false),
|
||||
constant("solid"),
|
||||
)
|
||||
}
|
||||
targetWidth={300}
|
||||
targetHeight={200}
|
||||
expanded={false}
|
||||
@@ -132,7 +144,13 @@ test("Screen share volume UI is hidden when screen share has no audio", async ()
|
||||
const toggleExpanded = vi.fn();
|
||||
const { container } = render(
|
||||
<SpotlightTile
|
||||
vm={new SpotlightTileViewModel(constant([vm]), constant(false))}
|
||||
vm={
|
||||
new SpotlightTileViewModel(
|
||||
constant([vm]),
|
||||
constant(false),
|
||||
constant("solid"),
|
||||
)
|
||||
}
|
||||
targetWidth={300}
|
||||
targetHeight={200}
|
||||
expanded={false}
|
||||
@@ -168,7 +186,13 @@ test("SpotlightTile displays ringing media", async () => {
|
||||
const toggleExpanded = vi.fn();
|
||||
const { container } = render(
|
||||
<SpotlightTile
|
||||
vm={new SpotlightTileViewModel(constant([vm]), constant(false))}
|
||||
vm={
|
||||
new SpotlightTileViewModel(
|
||||
constant([vm]),
|
||||
constant(false),
|
||||
constant("solid"),
|
||||
)
|
||||
}
|
||||
targetWidth={300}
|
||||
targetHeight={200}
|
||||
expanded={false}
|
||||
|
||||
@@ -65,6 +65,7 @@ interface SpotlightItemBaseProps {
|
||||
displayName: string;
|
||||
mxcAvatarUrl: string | undefined;
|
||||
showNameTags: boolean;
|
||||
background: "solid" | "transparent";
|
||||
focusable: boolean;
|
||||
"aria-hidden"?: boolean;
|
||||
}
|
||||
@@ -78,6 +79,7 @@ interface SpotlightMemberMediaItemBaseProps extends SpotlightItemBaseProps {
|
||||
interface SpotlightUserMediaItemBaseProps extends SpotlightMemberMediaItemBaseProps {
|
||||
videoFit: "contain" | "cover";
|
||||
videoEnabled: boolean;
|
||||
soundWaves: boolean | undefined;
|
||||
}
|
||||
|
||||
interface SpotlightLocalUserMediaItemProps extends SpotlightUserMediaItemBaseProps {
|
||||
@@ -120,6 +122,7 @@ const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
||||
}) => {
|
||||
const videoFit = useBehavior(vm.videoFit$);
|
||||
const videoEnabled = useBehavior(vm.videoEnabled$);
|
||||
const speaking = useBehavior(vm.speaking$);
|
||||
|
||||
// Whenever target bounds change, inform the viewModel
|
||||
useEffect(() => {
|
||||
@@ -132,6 +135,7 @@ const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
||||
RefAttributes<HTMLDivElement> = {
|
||||
videoFit,
|
||||
videoEnabled,
|
||||
soundWaves: props.background === "transparent" ? speaking : undefined,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
...props,
|
||||
@@ -243,12 +247,14 @@ interface SpotlightItemProps {
|
||||
targetHeight: number;
|
||||
showNameTags: boolean;
|
||||
showRingingStatus: boolean;
|
||||
background: "solid" | "transparent";
|
||||
focusable: boolean;
|
||||
intersectionObserver$: Observable<IntersectionObserver>;
|
||||
/**
|
||||
* Whether this item should act as a scroll snapping point.
|
||||
*/
|
||||
snap: boolean;
|
||||
className?: string;
|
||||
"aria-hidden"?: boolean;
|
||||
}
|
||||
|
||||
@@ -259,9 +265,11 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
|
||||
targetHeight,
|
||||
showNameTags,
|
||||
showRingingStatus,
|
||||
background,
|
||||
focusable,
|
||||
intersectionObserver$,
|
||||
snap,
|
||||
className,
|
||||
"aria-hidden": ariaHidden,
|
||||
}) => {
|
||||
const ourRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -288,13 +296,14 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
|
||||
const baseProps: SpotlightItemBaseProps & RefAttributes<HTMLDivElement> = {
|
||||
ref,
|
||||
"data-id": vm.id,
|
||||
className: classNames(styles.item, { [styles.snap]: snap }),
|
||||
className: classNames(className, styles.item, { [styles.snap]: snap }),
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
userId: vm.userId,
|
||||
displayName,
|
||||
mxcAvatarUrl,
|
||||
showNameTags,
|
||||
background,
|
||||
focusable,
|
||||
"aria-hidden": ariaHidden,
|
||||
};
|
||||
@@ -391,6 +400,10 @@ interface Props {
|
||||
showRingingStatus: boolean;
|
||||
focusable: boolean;
|
||||
className?: string;
|
||||
/**
|
||||
* CSS class of the individual spotlight items.
|
||||
*/
|
||||
itemClassName?: string;
|
||||
style?: ComponentProps<typeof animated.div>["style"];
|
||||
}
|
||||
|
||||
@@ -406,12 +419,14 @@ export const SpotlightTile: FC<Props> = ({
|
||||
showRingingStatus,
|
||||
focusable = true,
|
||||
className,
|
||||
itemClassName,
|
||||
style,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [ourRef, root$] = useObservableRef<HTMLDivElement | null>(null);
|
||||
const ref = useMergedRefs(ourRef, theirRef);
|
||||
const maximised = useBehavior(vm.maximised$);
|
||||
const background = useBehavior(vm.background$);
|
||||
const media = useBehavior(vm.media$);
|
||||
const [visibleId, setVisibleId] = useState<string | undefined>(media[0]?.id);
|
||||
const latestMedia = useLatest(media);
|
||||
@@ -492,9 +507,8 @@ export const SpotlightTile: FC<Props> = ({
|
||||
return (
|
||||
<animated.div
|
||||
ref={ref}
|
||||
className={classNames(className, styles.tile, {
|
||||
[styles.maximised]: maximised,
|
||||
})}
|
||||
className={classNames(className, styles.tile)}
|
||||
data-maximised={maximised}
|
||||
style={style}
|
||||
>
|
||||
{canGoBack && (
|
||||
@@ -516,6 +530,7 @@ export const SpotlightTile: FC<Props> = ({
|
||||
targetHeight={targetHeight}
|
||||
showRingingStatus={showRingingStatus}
|
||||
showNameTags={showNameTags}
|
||||
background={background}
|
||||
focusable={focusable}
|
||||
intersectionObserver$={intersectionObserver$}
|
||||
// This is how we get the container to scroll to the right media
|
||||
@@ -523,6 +538,7 @@ export const SpotlightTile: FC<Props> = ({
|
||||
// remove all scroll snap points except for just the one media
|
||||
// that we want to bring into view
|
||||
snap={scrollToId === null || scrollToId === vm.id}
|
||||
className={itemClassName}
|
||||
aria-hidden={(scrollToId ?? visibleId) !== vm.id}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -6,22 +6,22 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { expect, describe, it } from "vitest";
|
||||
import { render } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
import { TileAvatar } from "./TileAvatar";
|
||||
|
||||
describe("TileAvatar", () => {
|
||||
it("should show loading spinner when loading", () => {
|
||||
const { container } = render(
|
||||
render(
|
||||
<TileAvatar id="@a:example.org" name="Alice" size={96} loading={true} />,
|
||||
);
|
||||
expect(container.querySelector(".loading")).toBeInTheDocument();
|
||||
screen.getByLabelText("Loading…");
|
||||
});
|
||||
|
||||
it("should not show loading spinner when not loading", () => {
|
||||
const { container } = render(
|
||||
render(
|
||||
<TileAvatar id="@a:example.org" name="Alice" size={96} loading={false} />,
|
||||
);
|
||||
expect(container.querySelector(".loading")).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("Loading…")).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import { type FC } from "react";
|
||||
import { InlineSpinner } from "@vector-im/compound-web";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./TileAvatar.module.css";
|
||||
import { Avatar, type Props as AvatarProps } from "../Avatar";
|
||||
@@ -17,11 +18,12 @@ interface Props extends AvatarProps {
|
||||
}
|
||||
|
||||
export const TileAvatar: FC<Props> = ({ size, loading, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
{loading && (
|
||||
<div className={styles.loading}>
|
||||
<InlineSpinner size={size / 3} />
|
||||
<InlineSpinner size={size / 3} aria-label={t("common.loading")} />
|
||||
</div>
|
||||
)}
|
||||
<Avatar size={size} {...props} />
|
||||
|
||||
@@ -18,12 +18,7 @@ export default defineConfig((configEnv) =>
|
||||
extends: true,
|
||||
test: {
|
||||
name: "unit",
|
||||
css: {
|
||||
include: /.+/,
|
||||
modules: {
|
||||
classNameStrategy: "non-scoped",
|
||||
},
|
||||
},
|
||||
css: { include: /.+/ },
|
||||
setupFiles: ["src/vitest.setup.ts"],
|
||||
environment: "jsdom",
|
||||
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
|
||||
|
||||
Reference in New Issue
Block a user