From e5117b962c14906211194daa9b42caf434dcb0bd Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 21 Nov 2024 12:51:22 -0500 Subject: [PATCH 01/72] Enable audio output support on Firefox --- src/livekit/MediaDevicesContext.tsx | 18 +++--------------- src/settings/SettingsModal.tsx | 11 ++++------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index d405eec0..334eb2ec 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -26,7 +26,6 @@ import { videoInput as videoInputSetting, Setting, } from "../settings/settings"; -import { isFirefox } from "../Platform"; export interface MediaDevice { available: MediaDeviceInfo[]; @@ -61,7 +60,6 @@ function useMediaDevice( kind: MediaDeviceKind, setting: Setting, usingNames: boolean, - alwaysDefault: boolean = false, ): MediaDevice { // Make sure we don't needlessly reset to a device observer without names, // once permissions are already given @@ -89,7 +87,7 @@ function useMediaDevice( return useMemo(() => { let selectedId: string | undefined = undefined; - if (!alwaysDefault && available) { + if (available) { // If the preferred device is available, use it. Or if every available // device ID is falsy, the browser is probably just being paranoid about // fingerprinting and we should still try using the preferred device. @@ -112,7 +110,7 @@ function useMediaDevice( selectedId, select, }; - }, [available, preferredId, select, alwaysDefault]); + }, [available, preferredId, select]); } const deviceStub: MediaDevice = { @@ -139,15 +137,6 @@ export const MediaDevicesProvider: FC = ({ children }) => { const [numCallersUsingNames, setNumCallersUsingNames] = useState(0); const usingNames = numCallersUsingNames > 0; - // Setting the audio device to something other than 'undefined' breaks echo-cancellation - // and even can introduce multiple different output devices for one call. - const alwaysUseDefaultAudio = isFirefox(); - - // On FF we dont need to query the names - // (call enumerateDevices + create meadia stream to trigger permissions) - // for ouput devices because the selector wont be shown on FF. - const useOutputNames = usingNames && !isFirefox(); - const audioInput = useMediaDevice( "audioinput", audioInputSetting, @@ -156,8 +145,7 @@ export const MediaDevicesProvider: FC = ({ children }) => { const audioOutput = useMediaDevice( "audiooutput", audioOutputSetting, - useOutputNames, - alwaysUseDefaultAudio, + usingNames, ); const videoInput = useMediaDevice( "videoinput", diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 78afc2c5..79545f1d 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -29,7 +29,6 @@ import { useOptInAnalytics, soundEffectVolumeSetting, } from "./settings"; -import { isFirefox } from "../Platform"; import { PreferencesSettingsTab } from "./PreferencesSettingsTab"; import { Slider } from "../Slider"; import { DeviceSelection } from "./DeviceSelection"; @@ -96,12 +95,10 @@ export const SettingsModal: FC = ({ devices={devices.audioInput} caption={t("common.microphone")} /> - {!isFirefox() && ( - - )} +

{t("settings.audio_tab.effect_volume_description")}

From f249b7d46386aee15505a4c1f3a78f0479955c48 Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 21 Nov 2024 14:43:30 -0500 Subject: [PATCH 02/72] Create a virtual default audio output Managing your audio output manually is kind of cumbersome; Chrome creates a default audio output for us, but now that audio outputs are enabled on Firefox as well, I find it necessary for a good user experience that there always be a way to set it to "whatever the default is". --- locales/en-GB/app.json | 14 ++-- src/livekit/MediaDevicesContext.tsx | 105 ++++++++++++++++++---------- src/livekit/useLiveKit.ts | 6 +- src/room/MuteStates.ts | 4 +- src/settings/DeviceSelection.tsx | 31 +++++--- src/settings/SettingsModal.tsx | 11 ++- 6 files changed, 109 insertions(+), 62 deletions(-) diff --git a/locales/en-GB/app.json b/locales/en-GB/app.json index 0b9142d2..d71b8c9c 100644 --- a/locales/en-GB/app.json +++ b/locales/en-GB/app.json @@ -48,13 +48,11 @@ "audio": "Audio", "avatar": "Avatar", "back": "Back", - "camera": "Camera", "display_name": "Display name", "encrypted": "Encrypted", "error": "Error", "home": "Home", "loading": "Loading…", - "microphone": "Microphone", "next": "Next", "options": "Options", "password": "Password", @@ -149,6 +147,15 @@ "developer_settings_label": "Developer Settings", "developer_settings_label_description": "Expose developer settings in the settings window.", "developer_tab_title": "Developer", + "devices": { + "camera": "Camera", + "camera_numbered": "Camera {{n}}", + "default": "Default", + "microphone": "Microphone", + "microphone_numbered": "Microphone {{n}}", + "speaker": "Speaker", + "speaker_numbered": "Speaker {{n}}" + }, "duplicate_tiles_label": "Number of additional tile copies per participant", "feedback_tab_body": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.", "feedback_tab_description_label": "Your feedback", @@ -168,8 +175,7 @@ "preferences_tab_body": "Here you can configure extra options for an improved experience", "preferences_tab_h4": "Preferences", "preferences_tab_show_hand_raised_timer_description": "Show a timer when a participant raises their hand", - "preferences_tab_show_hand_raised_timer_label": "Show hand raise duration", - "speaker_device_selection_label": "Speaker" + "preferences_tab_show_hand_raised_timer_label": "Show hand raise duration" }, "star_rating_input_label_one": "{{count}} stars", "star_rating_input_label_other": "{{count}} stars", diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 334eb2ec..5382b331 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -16,7 +16,8 @@ import { useState, } from "react"; import { createMediaDeviceObserver } from "@livekit/components-core"; -import { Observable } from "rxjs"; +import { startWith } from "rxjs"; +import { useObservableEagerState } from "observable-hooks"; import { logger } from "matrix-js-sdk/src/logger"; import { @@ -27,9 +28,24 @@ import { Setting, } from "../settings/settings"; +export type DeviceLabel = + | { type: "name"; name: string } + | { type: "number"; number: number } + | { type: "default" }; + export interface MediaDevice { - available: MediaDeviceInfo[]; + /** + * A map from available device IDs to labels. + */ + available: Map; selectedId: string | undefined; + /** + * The group ID of the selected device. + */ + // This is exposed sort of ad-hoc because it's only needed for knowing when to + // restart the tracks of default input devices, and ideally this behavior + // would be encapsulated somehow… + selectedGroupId: string | undefined; select: (deviceId: string) => void; } @@ -41,21 +57,6 @@ export interface MediaDevices { stopUsingDeviceNames: () => void; } -// Cargo-culted from @livekit/components-react -function useObservableState( - observable: Observable | undefined, - startWith: T, -): T { - const [state, setState] = useState(startWith); - useEffect(() => { - // observable state doesn't run in SSR - if (typeof window === "undefined" || !observable) return; - const subscription = observable.subscribe(setState); - return (): void => subscription.unsubscribe(); - }, [observable]); - return state; -} - function useMediaDevice( kind: MediaDeviceKind, setting: Setting, @@ -79,43 +80,73 @@ function useMediaDevice( kind, () => logger.error("Error creating MediaDeviceObserver"), requestPermissions, - ), + ).pipe(startWith([])), [kind, requestPermissions], ); - const available = useObservableState(deviceObserver, []); - const [preferredId, select] = useSetting(setting); + const availableRaw = useObservableEagerState(deviceObserver); + const available = useMemo(() => { + // Sometimes browsers (particularly Firefox) can return multiple device + // entries for the exact same device ID; using a map deduplicates them + let available = new Map( + availableRaw.map((d, i) => [ + d.deviceId, + d.label + ? { type: "name", name: d.label } + : { type: "number", number: i + 1 }, + ]), + ); + // Create a virtual default audio output for browsers that don't have one. + // Its device ID must be the empty string because that's what setSinkId + // recognizes. + if ( + kind === "audiooutput" && + available.size && + !available.has("") && + !available.has("default") + ) + available = new Map([["", { type: "default" }], ...available]); + // Note: creating virtual default input devices would be another problem + // entirely, because requesting a media stream from deviceId "" won't + // automatically track the default device. + return available; + }, [kind, availableRaw]); - return useMemo(() => { - let selectedId: string | undefined = undefined; - if (available) { + const [preferredId, select] = useSetting(setting); + const selectedId = useMemo(() => { + if (available.size) { // If the preferred device is available, use it. Or if every available // device ID is falsy, the browser is probably just being paranoid about // fingerprinting and we should still try using the preferred device. // Worst case it is not available and the browser will gracefully fall // back to some other device for us when requesting the media stream. // Otherwise, select the first available device. - selectedId = - available.some((d) => d.deviceId === preferredId) || - available.every((d) => d.deviceId === "") - ? preferredId - : available.at(0)?.deviceId; + return (preferredId !== undefined && available.has(preferredId)) || + (available.size === 1 && available.has("")) + ? preferredId + : available.keys().next().value; } + return undefined; + }, [available, preferredId]); + const selectedGroupId = useMemo( + () => availableRaw.find((d) => d.deviceId === selectedId)?.groupId, + [availableRaw, selectedId], + ); - return { - available: available - ? // Sometimes browsers (particularly Firefox) can return multiple - // device entries for the exact same device ID; deduplicate them - [...new Map(available.map((d) => [d.deviceId, d])).values()] - : [], + return useMemo( + () => ({ + available, selectedId, + selectedGroupId, select, - }; - }, [available, preferredId, select]); + }), + [available, selectedId, selectedGroupId, select], + ); } const deviceStub: MediaDevice = { - available: [], + available: new Map(), selectedId: undefined, + selectedGroupId: undefined, select: () => {}, }; const devicesStub: MediaDevices = { diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index 4645fab7..458ecaa0 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -290,18 +290,14 @@ export function useLiveKit( room.localParticipant.audioTrackPublications.values(), ).find((d) => d.source === Track.Source.Microphone)?.track; - const defaultDevice = device.available.find( - (d) => d.deviceId === "default", - ); if ( - defaultDevice && activeMicTrack && // only restart if the stream is still running: LiveKit will detect // when a track stops & restart appropriately, so this is not our job. // Plus, we need to avoid restarting again if the track is already in // the process of being restarted. activeMicTrack.mediaStreamTrack.readyState !== "ended" && - defaultDevice.groupId !== + device.selectedGroupId !== activeMicTrack.mediaStreamTrack.getSettings().groupId ) { // It's different, so restart the track, ie. cause Livekit to do another diff --git a/src/room/MuteStates.ts b/src/room/MuteStates.ts index 261be59e..5fcadc90 100644 --- a/src/room/MuteStates.ts +++ b/src/room/MuteStates.ts @@ -54,12 +54,12 @@ function useMuteState( ): MuteState { const [enabled, setEnabled] = useReactiveState( (prev) => - device.available.length > 0 ? (prev ?? enabledByDefault()) : undefined, + device.available.size > 0 ? (prev ?? enabledByDefault()) : undefined, [device], ); return useMemo( () => - device.available.length === 0 + device.available.size === 0 ? deviceUnavailable : { enabled: enabled ?? false, diff --git a/src/settings/DeviceSelection.tsx b/src/settings/DeviceSelection.tsx index 005973a0..c4020822 100644 --- a/src/settings/DeviceSelection.tsx +++ b/src/settings/DeviceSelection.tsx @@ -13,16 +13,23 @@ import { RadioControl, Separator, } from "@vector-im/compound-web"; +import { useTranslation } from "react-i18next"; import { MediaDevice } from "../livekit/MediaDevicesContext"; import styles from "./DeviceSelection.module.css"; interface Props { devices: MediaDevice; - caption: string; + title: string; + numberedLabel: (number: number) => string; } -export const DeviceSelection: FC = ({ devices, caption }) => { +export const DeviceSelection: FC = ({ + devices, + title, + numberedLabel, +}) => { + const { t } = useTranslation(); const groupId = useId(); const onChange = useCallback( (e: ChangeEvent) => { @@ -31,7 +38,7 @@ export const DeviceSelection: FC = ({ devices, caption }) => { [devices], ); - if (devices.available.length == 0) return null; + if (devices.available.size == 0) return null; return (
@@ -42,26 +49,28 @@ export const DeviceSelection: FC = ({ devices, caption }) => { as="h4" className={styles.title} > - {caption} + {title}
- {devices.available.map(({ deviceId, label }, index) => ( + {[...devices.available].map(([id, label]) => ( } > ))} diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 79545f1d..3ac980ca 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -93,11 +93,15 @@ export const SettingsModal: FC = ({
+ t("settings.devices.microphone_numbered", { n }) + } /> t("settings.devices.speaker_numbered", { n })} />
@@ -123,7 +127,8 @@ export const SettingsModal: FC = ({ t("settings.devices.camera_numbered", { n })} /> ), From cf174261c9fcae99c5312d01cd5c3c662d8dc02b Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 25 Nov 2024 20:22:02 +0000 Subject: [PATCH 03/72] Some simple initial tests for MediaView (#2813) * Some simple tests for MediaView * Use jest-dom assertions * Add tests for videoMuted * Add test case for placeholder video track * Revert yarn.lock changes * More revert * Deduplicate test case logic and improve names * Use role and label --- package.json | 1 + src/Avatar.tsx | 7 +- src/tile/MediaView.module.css | 9 --- src/tile/MediaView.test.tsx | 117 ++++++++++++++++++++++++++++++++++ src/tile/MediaView.tsx | 14 +++- src/vitest.setup.ts | 1 + yarn.lock | 51 ++++++++++++++- 7 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 src/tile/MediaView.test.tsx diff --git a/package.json b/package.json index 46a86b45..6d5fca42 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@sentry/react": "^8.0.0", "@sentry/vite-plugin": "^2.0.0", "@testing-library/dom": "^10.1.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.0", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.5.1", diff --git a/src/Avatar.tsx b/src/Avatar.tsx index a0ae1483..29ab5236 100644 --- a/src/Avatar.tsx +++ b/src/Avatar.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { useMemo, FC } from "react"; +import { useMemo, FC, CSSProperties } from "react"; import { Avatar as CompoundAvatar } from "@vector-im/compound-web"; import { getAvatarUrl } from "./utils/matrix"; @@ -33,6 +33,7 @@ interface Props { className?: string; src?: string; size?: Size | number; + style?: CSSProperties; } export const Avatar: FC = ({ @@ -41,6 +42,8 @@ export const Avatar: FC = ({ name, src, size = Size.MD, + style, + ...props }) => { const { client } = useClient(); @@ -64,6 +67,8 @@ export const Avatar: FC = ({ name={name} size={`${sizePx}px`} src={resolvedSrc} + style={style} + {...props} /> ); }; diff --git a/src/tile/MediaView.module.css b/src/tile/MediaView.module.css index 3ed6c83d..4594c284 100644 --- a/src/tile/MediaView.module.css +++ b/src/tile/MediaView.module.css @@ -34,10 +34,6 @@ Please see LICENSE in the repository root for full details. object-fit: contain; } -.media.videoMuted video { - display: none; -} - .bg { background-color: var(--cpd-color-bg-subtle-secondary); inline-size: 100%; @@ -47,7 +43,6 @@ Please see LICENSE in the repository root for full details. } .avatar { - display: none; position: absolute; top: 50%; left: 50%; @@ -55,10 +50,6 @@ Please see LICENSE in the repository root for full details. pointer-events: none; } -.media.videoMuted .avatar { - display: initial; -} - /* CSS makes us put a condition here, even though all we want to do is unconditionally select the container so we can use cqmin units */ @container mediaView (width > 0) { diff --git a/src/tile/MediaView.test.tsx b/src/tile/MediaView.test.tsx new file mode 100644 index 00000000..238ffdd1 --- /dev/null +++ b/src/tile/MediaView.test.tsx @@ -0,0 +1,117 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { describe, expect, test } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { axe } from "vitest-axe"; +import { TooltipProvider } from "@vector-im/compound-web"; +import { + TrackReference, + TrackReferencePlaceholder, +} from "@livekit/components-core"; +import { Track, TrackPublication } from "livekit-client"; +import { type ComponentProps } from "react"; + +import { MediaView } from "./MediaView"; +import { EncryptionStatus } from "../state/MediaViewModel"; +import { mockLocalParticipant } from "../utils/test"; + +describe("MediaView", () => { + const participant = mockLocalParticipant({}); + const trackReferencePlaceholder: TrackReferencePlaceholder = { + participant, + source: Track.Source.Camera, + }; + const trackReference: TrackReference = { + ...trackReferencePlaceholder, + publication: new TrackPublication(Track.Kind.Video, "id", "name"), + }; + + const baseProps: ComponentProps = { + displayName: "some name", + videoEnabled: true, + videoFit: "contain", + targetWidth: 300, + targetHeight: 200, + encryptionStatus: EncryptionStatus.Connecting, + mirror: false, + unencryptedWarning: false, + video: trackReference, + member: undefined, + }; + + test("is accessible", async () => { + const { container } = render(); + expect(await axe(container)).toHaveNoViolations(); + }); + + describe("placeholder track", () => { + test("neither video nor avatar are shown", () => { + render(); + expect(screen.queryByTestId("video")).toBeNull(); + expect(screen.queryAllByRole("img", { name: "some name" }).length).toBe( + 0, + ); + }); + }); + + describe("name tag", () => { + test("is shown with name", () => { + render(); + expect(screen.getByTestId("name_tag")).toHaveTextContent("Bob"); + }); + }); + + describe("unencryptedWarning", () => { + test("is shown and accessible", async () => { + const { container } = render( + + + , + ); + expect(await axe(container)).toHaveNoViolations(); + expect( + screen.getByRole("img", { name: "common.unencrypted" }), + ).toBeTruthy(); + }); + + test("is not shown", () => { + render( + + + , + ); + expect( + screen.queryAllByRole("img", { name: "common.unencrypted" }).length, + ).toBe(0); + }); + }); + + describe("videoEnabled", () => { + test("just video is visible", () => { + render( + + + , + ); + expect(screen.getByTestId("video")).toBeVisible(); + expect(screen.queryAllByRole("img", { name: "some name" }).length).toBe( + 0, + ); + }); + + test("just avatar is visible", () => { + render( + + + , + ); + expect(screen.getByRole("img", { name: "some name" })).toBeVisible(); + expect(screen.getByTestId("video")).not.toBeVisible(); + }); + }); +}); diff --git a/src/tile/MediaView.tsx b/src/tile/MediaView.tsx index 9707e707..4e69bf41 100644 --- a/src/tile/MediaView.tsx +++ b/src/tile/MediaView.tsx @@ -76,7 +76,6 @@ export const MediaView = forwardRef( ( size={avatarSize} src={member?.getMxcAvatarUrl()} className={styles.avatar} + style={{ display: videoEnabled ? "none" : "initial" }} /> {video.publication !== undefined && ( ( // There's no reason for this to be focusable tabIndex={-1} disablePictureInPicture + style={{ display: videoEnabled ? "block" : "none" }} + data-testid="video" /> )}
@@ -133,7 +135,13 @@ export const MediaView = forwardRef( )*/}
{nameTagLeadingIcon} - + {displayName} {unencryptedWarning && ( @@ -146,6 +154,8 @@ export const MediaView = forwardRef( width={20} height={20} className={styles.errorIcon} + role="img" + aria-label={t("common.unencrypted")} /> )} diff --git a/src/vitest.setup.ts b/src/vitest.setup.ts index 421ec663..38b8704e 100644 --- a/src/vitest.setup.ts +++ b/src/vitest.setup.ts @@ -15,6 +15,7 @@ import { afterEach } from "vitest"; import { cleanup } from "@testing-library/react"; import "vitest-axe/extend-expect"; import { logger } from "matrix-js-sdk/src/logger"; +import "@testing-library/jest-dom/vitest"; import EN_GB from "../locales/en-GB/app.json"; import { Config } from "./config/Config"; diff --git a/yarn.lock b/yarn.lock index 96ee1ae4..06f07543 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,6 +40,11 @@ resolved "https://registry.yarnpkg.com/@actions/io/-/io-1.1.3.tgz#4cdb6254da7962b07473ff5c335f3da485d94d71" integrity sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q== +"@adobe/css-tools@^4.4.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.1.tgz#2447a230bfe072c1659e6815129c03cf170710e3" + integrity sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ== + "@ampproject/remapping@^2.2.0", "@ampproject/remapping@^2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -2914,6 +2919,19 @@ lz-string "^1.5.0" pretty-format "^27.0.2" +"@testing-library/jest-dom@^6.6.3": + version "6.6.3" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2" + integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + "@testing-library/react-hooks@^8.0.1": version "8.0.1" resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" @@ -3527,7 +3545,7 @@ aria-query@5.3.0: dependencies: dequal "^2.0.3" -aria-query@^5.3.2: +aria-query@^5.0.0, aria-query@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== @@ -3928,6 +3946,14 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" @@ -4206,6 +4232,11 @@ css-what@^6.1.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + cssdb@^8.2.1: version "8.2.1" resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-8.2.1.tgz#62a5d9a41e2c86f1d7c35981098fc5ce47c5766c" @@ -4400,6 +4431,11 @@ dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-serializer@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" @@ -6118,6 +6154,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + loglevel@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7" @@ -7269,6 +7310,14 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + reflect.getprototypeof@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" From f7c7f41afdec0e6d4da81751bcb1f7b66fabedb7 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 27 Nov 2024 20:43:56 +0000 Subject: [PATCH 04/72] Upgrade js-sdk to develop with MSC4222 state_after support (#2841) Diff https://github.com/matrix-org/matrix-js-sdk/compare/2210255d6ffc909c574fb8ef16f92140b2fb7797...8fc77c595aa651db9f402df798b868ed076590e3 --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6d5fca42..ebaa59ff 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "livekit-client": "^2.5.7", "lodash-es": "^4.17.21", "loglevel": "^1.9.1", - "matrix-js-sdk": "matrix-org/matrix-js-sdk#2210255d6ffc909c574fb8ef16f92140b2fb7797", + "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^1.10.0", "normalize.css": "^8.0.1", "observable-hooks": "^4.2.3", diff --git a/yarn.lock b/yarn.lock index 06f07543..759a021d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6258,9 +6258,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@matrix-org/matrix-js-sdk#2210255d6ffc909c574fb8ef16f92140b2fb7797: +matrix-js-sdk@matrix-org/matrix-js-sdk#develop: version "34.12.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2210255d6ffc909c574fb8ef16f92140b2fb7797" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8fc77c595aa651db9f402df798b868ed076590e3" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" From 28da8c48da2e3bdc9dcebf99c731ce9208f6ce11 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Thu, 28 Nov 2024 18:05:12 +0100 Subject: [PATCH 05/72] Mute state improvements including muting if skipLobby=true in SPA (Based on #2834) (#2846) --- docs/url-params.md | 4 +- src/livekit/MediaDevicesContext.tsx | 2 +- src/room/GroupCallView.tsx | 23 ++-- src/room/MuteStates.test.tsx | 172 ++++++++++++++++++++++++++++ src/room/MuteStates.ts | 12 +- src/rtcSessionHelper.test.ts | 6 +- src/utils/test.ts | 9 ++ 7 files changed, 203 insertions(+), 25 deletions(-) create mode 100644 src/room/MuteStates.test.tsx diff --git a/docs/url-params.md b/docs/url-params.md index 267c65f6..010a4ec8 100644 --- a/docs/url-params.md +++ b/docs/url-params.md @@ -42,7 +42,7 @@ There are two formats for Element Call urls. | `deviceId` | Matrix device ID | Yes | Not applicable | The Matrix device ID for the widget host. | | `displayName` | | No | No | Display name used for auto-registration. | | `enableE2EE` (deprecated) | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Legacy flag to enable end-to-end encryption, not used in the `livekit` branch. | -| `fontScale` | A decimal number such as `0.9` | No | No | Factor by which to scale the interface's font size. | +| `fontScale` | A decimal number such as `0.9` | No, defaults to `1.0` | No, defaults to `1.0` | Factor by which to scale the interface's font size. | | `fonts` | | No | No | Defines the font(s) used by the interface. Multiple font parameters can be specified: `?font=font-one&font=font-two...`. | | `hideHeader` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the room header when in a call. | | `hideScreensharing` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the screen-sharing button. | @@ -55,7 +55,7 @@ There are two formats for Element Call urls. | `returnToLobby` | `true` or `false` | No, defaults to `false` | Not applicable | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. | | `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. | | `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` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. | +| `skipLobby` | `true` or `false` | No, 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. | | `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | Not applicable | The Matrix user ID. | | `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. | diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index d405eec0..4977f021 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -128,7 +128,7 @@ const devicesStub: MediaDevices = { stopUsingDeviceNames: () => {}, }; -const MediaDevicesContext = createContext(devicesStub); +export const MediaDevicesContext = createContext(devicesStub); interface Props { children: JSX.Element; diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index cc4fea07..b0fa8c69 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -131,48 +131,46 @@ export const GroupCallView: FC = ({ const latestDevices = useRef(); latestDevices.current = deviceContext; + // TODO: why do we use a ref here instead of using muteStates directly? const latestMuteStates = useRef(); latestMuteStates.current = muteStates; useEffect(() => { - const defaultDeviceSetup = async ( - requestedDeviceData: JoinCallData, - ): Promise => { + const defaultDeviceSetup = async ({ + audioInput, + videoInput, + }: JoinCallData): Promise => { // XXX: I think this is broken currently - LiveKit *won't* request // permissions and give you device names unless you specify a kind, but // here we want all kinds of devices. This needs a fix in livekit-client // for the following name-matching logic to do anything useful. const devices = await Room.getLocalDevices(undefined, true); - const { audioInput, videoInput } = requestedDeviceData; - if (audioInput === null) { - latestMuteStates.current!.audio.setEnabled?.(false); - } else { + + if (audioInput) { const deviceId = findDeviceByName(audioInput, "audioinput", devices); if (!deviceId) { logger.warn("Unknown audio input: " + audioInput); + // override the default mute state latestMuteStates.current!.audio.setEnabled?.(false); } else { logger.debug( `Found audio input ID ${deviceId} for name ${audioInput}`, ); latestDevices.current!.audioInput.select(deviceId); - latestMuteStates.current!.audio.setEnabled?.(true); } } - if (videoInput === null) { - latestMuteStates.current!.video.setEnabled?.(false); - } else { + if (videoInput) { const deviceId = findDeviceByName(videoInput, "videoinput", devices); if (!deviceId) { logger.warn("Unknown video input: " + videoInput); + // override the default mute state latestMuteStates.current!.video.setEnabled?.(false); } else { logger.debug( `Found video input ID ${deviceId} for name ${videoInput}`, ); latestDevices.current!.videoInput.select(deviceId); - latestMuteStates.current!.video.setEnabled?.(true); } } }; @@ -199,7 +197,6 @@ export const GroupCallView: FC = ({ } else { // No lobby and no preload: we enter the rtc session right away (async (): Promise => { - await defaultDeviceSetup({ audioInput: null, videoInput: null }); await enterRTCSession(rtcSession, perParticipantE2EE); })().catch((e) => { logger.error("Error joining RTC session", e); diff --git a/src/room/MuteStates.test.tsx b/src/room/MuteStates.test.tsx new file mode 100644 index 00000000..6cc5815c --- /dev/null +++ b/src/room/MuteStates.test.tsx @@ -0,0 +1,172 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; +import React, { ReactNode } from "react"; +import { beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; + +import { useMuteStates } from "./MuteStates"; +import { + MediaDevice, + MediaDevices, + MediaDevicesContext, +} from "../livekit/MediaDevicesContext"; +import { mockConfig } from "../utils/test"; + +function TestComponent(): ReactNode { + const muteStates = useMuteStates(); + return ( +
+
+ {muteStates.audio.enabled.toString()} +
+
+ {muteStates.video.enabled.toString()} +
+
+ ); +} + +const mockMicrophone: MediaDeviceInfo = { + deviceId: "", + kind: "audioinput", + label: "", + groupId: "", + toJSON() { + return {}; + }, +}; + +const mockSpeaker: MediaDeviceInfo = { + deviceId: "", + kind: "audiooutput", + label: "", + groupId: "", + toJSON() { + return {}; + }, +}; + +const mockCamera: MediaDeviceInfo = { + deviceId: "", + kind: "videoinput", + label: "", + groupId: "", + toJSON() { + return {}; + }, +}; + +function mockDevices(available: MediaDeviceInfo[]): MediaDevice { + return { + available, + selectedId: "", + select: (): void => {}, + }; +} + +function mockMediaDevices( + { + microphone, + speaker, + camera, + }: { + microphone?: boolean; + speaker?: boolean; + camera?: boolean; + } = { microphone: true, speaker: true, camera: true }, +): MediaDevices { + return { + audioInput: mockDevices(microphone ? [mockMicrophone] : []), + audioOutput: mockDevices(speaker ? [mockSpeaker] : []), + videoInput: mockDevices(camera ? [mockCamera] : []), + startUsingDeviceNames: (): void => {}, + stopUsingDeviceNames: (): void => {}, + }; +} + +describe("useMuteStates", () => { + beforeEach(() => { + vi.spyOn(React, "useContext").mockReturnValue({}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + afterAll(() => { + vi.clearAllMocks(); + }); + + it("disabled when no input devices", () => { + mockConfig(); + + render( + + + + + , + ); + expect(screen.getByTestId("audio-enabled").textContent).toBe("false"); + expect(screen.getByTestId("video-enabled").textContent).toBe("false"); + }); + + it("should be enabled by default", () => { + mockConfig(); + + render( + + + + + , + ); + expect(screen.getByTestId("audio-enabled").textContent).toBe("true"); + expect(screen.getByTestId("video-enabled").textContent).toBe("true"); + }); + + it("uses defaults from config", () => { + mockConfig({ + media_devices: { + enable_audio: false, + enable_video: false, + }, + }); + + render( + + + + + , + ); + expect(screen.getByTestId("audio-enabled").textContent).toBe("false"); + expect(screen.getByTestId("video-enabled").textContent).toBe("false"); + }); + + it("skipLobby mutes inputs", () => { + mockConfig(); + + render( + + + + + , + ); + expect(screen.getByTestId("audio-enabled").textContent).toBe("false"); + expect(screen.getByTestId("video-enabled").textContent).toBe("false"); + }); +}); diff --git a/src/room/MuteStates.ts b/src/room/MuteStates.ts index 261be59e..1452c250 100644 --- a/src/room/MuteStates.ts +++ b/src/room/MuteStates.ts @@ -19,6 +19,7 @@ import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext"; import { useReactiveState } from "../useReactiveState"; import { ElementWidgetActions, widget } from "../widget"; import { Config } from "../config/Config"; +import { useUrlParams } from "../UrlParams"; /** * If there already are this many participants in the call, we automatically mute @@ -72,13 +73,14 @@ function useMuteState( export function useMuteStates(): MuteStates { const devices = useMediaDevices(); - const audio = useMuteState( - devices.audioInput, - () => Config.get().media_devices.enable_audio, - ); + const { skipLobby } = useUrlParams(); + + const audio = useMuteState(devices.audioInput, () => { + return Config.get().media_devices.enable_audio && !skipLobby; + }); const video = useMuteState( devices.videoInput, - () => Config.get().media_devices.enable_video, + () => Config.get().media_devices.enable_video && !skipLobby, ); useEffect(() => { diff --git a/src/rtcSessionHelper.test.ts b/src/rtcSessionHelper.test.ts index a7c8781f..61cd990e 100644 --- a/src/rtcSessionHelper.test.ts +++ b/src/rtcSessionHelper.test.ts @@ -9,8 +9,7 @@ import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { expect, test, vi } from "vitest"; import { enterRTCSession } from "../src/rtcSessionHelpers"; -import { Config } from "../src/config/Config"; -import { DEFAULT_CONFIG } from "./config/ConfigOptions"; +import { mockConfig } from "./utils/test"; test("It joins the correct Session", async () => { const focusFromOlderMembership = { @@ -34,8 +33,7 @@ test("It joins the correct Session", async () => { ], }; - vi.spyOn(Config, "get").mockReturnValue({ - ...DEFAULT_CONFIG, + mockConfig({ livekit: { livekit_service_url: "http://my-default-service-url.com" }, }); const mockedSession = vi.mocked({ diff --git a/src/utils/test.ts b/src/utils/test.ts index 5988dd6f..f583f797 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -21,6 +21,8 @@ import { RemoteUserMediaViewModel, } from "../state/MediaViewModel"; import { E2eeType } from "../e2ee/e2eeType"; +import { DEFAULT_CONFIG, ResolvedConfigOptions } from "../config/ConfigOptions"; +import { Config } from "../config/Config"; export function withFakeTimers(continuation: () => void): void { vi.useFakeTimers(); @@ -197,3 +199,10 @@ export async function withRemoteMedia( vm.destroy(); } } + +export function mockConfig(config: Partial = {}): void { + vi.spyOn(Config, "get").mockReturnValue({ + ...DEFAULT_CONFIG, + ...config, + }); +} From 65e962bf1b6d1dfb8feead44f119a407c045c89f Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Thu, 28 Nov 2024 18:24:24 +0100 Subject: [PATCH 06/72] Fix wrong hand raise margins in pip tile. (#2840) --- src/reactions/ReactionIndicator.module.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/reactions/ReactionIndicator.module.css b/src/reactions/ReactionIndicator.module.css index cc05ab1a..32b7fa3d 100644 --- a/src/reactions/ReactionIndicator.module.css +++ b/src/reactions/ReactionIndicator.module.css @@ -1,6 +1,6 @@ .reactionIndicatorWidget { display: flex; - /* background-color: var(--cpd-color-bg-subtle-primary); */ + background-color: #00000050; border-radius: var(--cpd-radius-pill-effect); box-shadow: 0 0 var(--cpd-space-2x) #00000040; background: "ffffff40"; @@ -14,12 +14,15 @@ margin-top: auto; margin-bottom: auto; width: 3em; + padding-right: var(--cpd-space-2x); + margin-left: calc(var(--cpd-space-2x) * -1); } .reactionIndicatorWidgetLarge > p { padding: var(--cpd-space-2x); padding-right: var(--cpd-space-4x); padding-left: 0; + margin-left: 0; } .reactionLarge { @@ -31,13 +34,11 @@ .reaction { margin: var(--cpd-space-1x); color: var(--cpd-color-icon-secondary); - /* background-color: var(--cpd-color-icon-secondary); */ display: flex; align-items: center; border-radius: var(--cpd-radius-pill-effect); user-select: none; overflow: hidden; - /* box-shadow: var(--small-drop-shadow); */ box-sizing: border-box; max-inline-size: 100%; max-width: fit-content; From 32ccfe8813a5c9e6ddc8ba96f6667adcf738f544 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 28 Nov 2024 17:40:06 +0000 Subject: [PATCH 07/72] Fix Cache-Control header for docker images (#2847) --- config/httpd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/httpd.conf b/config/httpd.conf index 597466e2..164a9b79 100644 --- a/config/httpd.conf +++ b/config/httpd.conf @@ -5,7 +5,7 @@ # disable cache entriely by default (apart from Etag which is accurate enough) - Header add Cache-Control "private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" + Header add Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" CacheDisable on ExpiresActive off From 82360c674c4b0c8715a3be924a79b6517e545052 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:44:54 +0000 Subject: [PATCH 08/72] Update dependency livekit-client to v2.7.0 --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 759a021d..ee2a8e42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6111,9 +6111,9 @@ lines-and-columns@^1.1.6: integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== livekit-client@^2.5.7: - version "2.6.3" - resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-2.6.3.tgz#0c7e16bcd8b30f61e867ba287257b60db69c7801" - integrity sha512-sUFjdERYdazGmYUCkxV46qKrL8Pg4Aw+9fs/DxV0EC/YtVd7zQh2QObip7IkyT8Ipj4gXhH8CkSinisZ1KpsJQ== + version "2.7.0" + resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-2.7.0.tgz#d7a80aff4ad335dd093b0c90d0d715466539651a" + integrity sha512-4vjfSReFNAUD+2oLUz9qFRWztJaI/+AexpOmCgizNsPYpvvqgAvEGxapnhuAug9uP7JVYaKPXaTCq90MWZoDHg== dependencies: "@livekit/mutex" "1.0.0" "@livekit/protocol" "1.24.0" @@ -7619,9 +7619,9 @@ scheduler@^0.23.2: loose-envify "^1.1.0" sdp-transform@^2.14.1: - version "2.14.2" - resolved "https://registry.yarnpkg.com/sdp-transform/-/sdp-transform-2.14.2.tgz#d2cee6a1f7abe44e6332ac6cbb94e8600f32d813" - integrity sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA== + version "2.15.0" + resolved "https://registry.yarnpkg.com/sdp-transform/-/sdp-transform-2.15.0.tgz#79d37a2481916f36a0534e07b32ceaa87f71df42" + integrity sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw== sdp@^3.2.0: version "3.2.0" From e12f4fada0cfe2359208c1649758b87661d68073 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:58:36 +0100 Subject: [PATCH 09/72] Bump js-sdk (#2850) - This fixes, that clients dont resend keys if other join right after someone else just left. --- yarn.lock | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/yarn.lock b/yarn.lock index ee2a8e42..73898d3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -939,10 +939,10 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.12.5": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" - integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== +"@babel/runtime@^7.12.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.8.4": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== dependencies: regenerator-runtime "^0.14.0" @@ -953,13 +953,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.8.4": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" - integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" @@ -1827,9 +1820,9 @@ "@bufbuild/protobuf" "^1.10.0" "@matrix-org/matrix-sdk-crypto-wasm@^9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-9.0.0.tgz#293fe8fcb9bc4d577c5f6cf2cbffa151c6e11329" - integrity sha512-dz4dkYXj6BeOQuw52XQj8dMuhi85pSFhfFeFlNRAO7JdRPhE9CHBrfK8knkZV5Zux5vvf3Ub4E7myoLeJgZoEw== + version "9.1.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-9.1.0.tgz#f889653eb4fafaad2a963654d586bd34de62acd5" + integrity sha512-CtPoNcoRW6ehwxpRQAksG3tR+NJ7k4DV02nMFYTDwQtie1V4R8OTY77BjEIs97NOblhtS26jU8m1lWsOBEz0Og== "@matrix-org/olm@3.2.15": version "3.2.15" @@ -6164,12 +6157,7 @@ loglevel@1.9.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7" integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg== -loglevel@^1.7.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" - integrity sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg== - -loglevel@^1.8.0, loglevel@^1.9.1: +loglevel@^1.7.1, loglevel@^1.8.0, loglevel@^1.9.1: version "1.9.2" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08" integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg== @@ -6260,7 +6248,7 @@ matrix-events-sdk@0.0.1: matrix-js-sdk@matrix-org/matrix-js-sdk#develop: version "34.12.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8fc77c595aa651db9f402df798b868ed076590e3" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/edac6a9983bd604c17535a9ae673dc979c7b61c4" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" @@ -6514,9 +6502,9 @@ observable-hooks@^4.2.3: integrity sha512-FdTQgyw1h5bG/QHCBIqctdBSnv9VARJCEilgpV6L2qlw1yeLqFIwPm4U15dMtl5kDmNN0hSt+Nl6iYbLFwEcQA== oidc-client-ts@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz#be264fb87c89f74f73863646431c32cd06f5ceb7" - integrity sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg== + version "3.1.0" + resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.1.0.tgz#28d3254951a1c64cc9780042c61492a71b2240dd" + integrity sha512-IDopEXjiwjkmJLYZo6BTlvwOtnlSniWZkKZoXforC/oLZHC9wkIxd25Kwtmo5yKFMMVcsp3JY6bhcNJqdYk8+g== dependencies: jwt-decode "^4.0.0" @@ -8407,9 +8395,9 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== uuid@11: - version "11.0.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.2.tgz#a8d68ba7347d051e7ea716cc8dcbbab634d66875" - integrity sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ== + version "11.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.3.tgz#248451cac9d1a4a4128033e765d137e2b2c49a3d" + integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg== validate-npm-package-license@^3.0.1: version "3.0.4" From 84b2dc8680ec50dbafce08e8e8bd6ef8d15134ec Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 29 Nov 2024 17:48:18 +0100 Subject: [PATCH 10/72] Use light background - the icons and the font is dark so the bg should not be darkened. But making it lighter is making it easier to read. --- src/reactions/ReactionIndicator.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactions/ReactionIndicator.module.css b/src/reactions/ReactionIndicator.module.css index 32b7fa3d..0fba7351 100644 --- a/src/reactions/ReactionIndicator.module.css +++ b/src/reactions/ReactionIndicator.module.css @@ -1,6 +1,6 @@ .reactionIndicatorWidget { display: flex; - background-color: #00000050; + background-color: #00000030; border-radius: var(--cpd-radius-pill-effect); box-shadow: 0 0 var(--cpd-space-2x) #00000040; background: "ffffff40"; @@ -33,7 +33,7 @@ .reaction { margin: var(--cpd-space-1x); - color: var(--cpd-color-icon-secondary); + color: white; display: flex; align-items: center; border-radius: var(--cpd-radius-pill-effect); From e142493380d304858efa254b5695ecb7b1624767 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:10:20 +0100 Subject: [PATCH 11/72] Update all non-major dependencies (#2852) * Update all non-major dependencies * prettier --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Timo --- src/room/InCallView.module.css | 7 +- yarn.lock | 409 ++++++++++++++++----------------- 2 files changed, 203 insertions(+), 213 deletions(-) diff --git a/src/room/InCallView.module.css b/src/room/InCallView.module.css index fe973132..bf0aabf5 100644 --- a/src/room/InCallView.module.css +++ b/src/room/InCallView.module.css @@ -36,10 +36,9 @@ Please see LICENSE in the repository root for full details. inset-block-end: 0; z-index: 1; display: grid; - grid-template-columns: minmax(0, var(--inline-content-inset)) 1fr auto 1fr minmax( - 0, - var(--inline-content-inset) - ); + grid-template-columns: + minmax(0, var(--inline-content-inset)) + 1fr auto 1fr minmax(0, var(--inline-content-inset)); grid-template-areas: ". logo buttons layout ."; align-items: center; gap: var(--cpd-space-3x); diff --git a/yarn.lock b/yarn.lock index 73898d3a..5b75ebb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -80,7 +80,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== -"@babel/core@^7.16.5", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.25.2": +"@babel/core@^7.16.5", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== @@ -284,14 +284,7 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7": - version "7.26.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.1.tgz#44e02499960df2cdce2c456372a3e8e0c3c5c975" - integrity sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw== - dependencies: - "@babel/types" "^7.26.0" - -"@babel/parser@^7.10.3", "@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": +"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.20.7", "@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== @@ -691,14 +684,14 @@ dependencies: "@babel/plugin-transform-react-jsx" "^7.25.9" -"@babel/plugin-transform-react-jsx-self@^7.24.7": +"@babel/plugin-transform-react-jsx-self@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz#c0b6cae9c1b73967f7f9eb2fca9536ba2fad2858" integrity sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg== dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-transform-react-jsx-source@^7.24.7": +"@babel/plugin-transform-react-jsx-source@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz#4c6b8daa520b5f155b5fb55547d7c9fa91417503" integrity sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg== @@ -2657,56 +2650,56 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sentry-internal/browser-utils@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.40.0.tgz#972925a9d600723dd1a022297100e97e92f4c903" - integrity sha512-tx7gb/PWMbTEyil/XPETVeRUeS3nKHIvQY2omyebw30TbhyLnibPZsUmXJiaIysL5PcY3k9maub3W/o0Y37T7Q== +"@sentry-internal/browser-utils@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.41.0.tgz#9dc30a8c88aa6e1e542e5acae29ceabd1b377cc4" + integrity sha512-nU7Bn3jEUmf1QXRUT3j2ewUBlFJpe9vnAnjqpeVPDWTsVI52BwVNcJHuE37PrGs66OZ1ZkGMfKnQk43oCAa+oQ== dependencies: - "@sentry/core" "8.40.0" - "@sentry/types" "8.40.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" -"@sentry-internal/feedback@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.40.0.tgz#5549f73d32b9a2509ffb0a07bf462ed8085178ec" - integrity sha512-1O9F3z80HNE0VfepKS+v+dixdatNqWlrlwgvvWl4BGzzoA+XhqvZo+HWxiOt7yx7+k1TuZNrB6Gy3u/QvpozXA== +"@sentry-internal/feedback@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.41.0.tgz#9c3c95e6f7738a0d00fcb89061c284baef313ba2" + integrity sha512-bw+BrSNw8abOnu/IpD8YSbYubXkkT8jyNS7TM4e4UPZMuXcbtia7/r5d7kAiUfKv/sV5PNMlZLOk+EYJeLTANg== dependencies: - "@sentry/core" "8.40.0" - "@sentry/types" "8.40.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" -"@sentry-internal/replay-canvas@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.40.0.tgz#6de0d67ee2fe3e503c6f85faeefab5df742a3ebe" - integrity sha512-Zr+m/le0SH4RowZB7rBCM0aRnvH3wZTaOFhwUk03/oGf2BRcgKuDCUMjnXKC9MyOpmey7UYXkzb8ro+81R6Q8w== +"@sentry-internal/replay-canvas@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.41.0.tgz#9da984adc54fcd8ebe07cbbc13132fa78396dd01" + integrity sha512-lpgOBHWr1ZNxidD72A2pfoUMjIpwonOPYoQZWAHr86Oa3eIVQOyfklZlHW+gKPFl2/IEl9Lbtcke0JiDp3dkIQ== dependencies: - "@sentry-internal/replay" "8.40.0" - "@sentry/core" "8.40.0" - "@sentry/types" "8.40.0" + "@sentry-internal/replay" "8.41.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" -"@sentry-internal/replay@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.40.0.tgz#54c7f1e3d115f9324f34e1b8875a95463a23049f" - integrity sha512-0SaDsBCSWxNVgNmPKu23frrHEXzN/MKl0hIkfuO55vL5TgjLTwpgkf0Ne4rNvaZQ5omIKk9Qd63HuQP3PHAMaw== +"@sentry-internal/replay@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.41.0.tgz#b1112a52a0cf1727589ad4d42a8fac9f98f6d733" + integrity sha512-ByXEY7JI95y4Qr9fS3d28l9uuVU5Qa0HgL+xDmYElNx7CXz3Q9hFN6ibgUeC3h8BO5pDULxWNgAppl7FRY8N5w== dependencies: - "@sentry-internal/browser-utils" "8.40.0" - "@sentry/core" "8.40.0" - "@sentry/types" "8.40.0" + "@sentry-internal/browser-utils" "8.41.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" "@sentry/babel-plugin-component-annotate@2.22.6": version "2.22.6" resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.6.tgz#829d6caf2c95c1c46108336de4e1049e6521435e" integrity sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ== -"@sentry/browser@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.40.0.tgz#de7b4531be2ac4667755e9e1b5da3808851392ae" - integrity sha512-m/Yor6IDBeDHtQochu8n6z4HXrXkrPhu6+o5Ouve0Zi3ptthSoK1FOGvJxVBat3nRq0ydQyuuPuTB6WfdWbwHQ== +"@sentry/browser@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.41.0.tgz#f594012e6377a92db72127ef39aee812efe3a3b7" + integrity sha512-FfAU55eYwW2lG4M3dEw2472RvHrD5YWSfHCZvuRf/4skX38kFvKghZQ+epL+CVHTzvIRHOrbj8qQK6YLTGl9ew== dependencies: - "@sentry-internal/browser-utils" "8.40.0" - "@sentry-internal/feedback" "8.40.0" - "@sentry-internal/replay" "8.40.0" - "@sentry-internal/replay-canvas" "8.40.0" - "@sentry/core" "8.40.0" - "@sentry/types" "8.40.0" + "@sentry-internal/browser-utils" "8.41.0" + "@sentry-internal/feedback" "8.41.0" + "@sentry-internal/replay" "8.41.0" + "@sentry-internal/replay-canvas" "8.41.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" "@sentry/bundler-plugin-core@2.22.6": version "2.22.6" @@ -2776,27 +2769,27 @@ "@sentry/cli-win32-i686" "2.38.1" "@sentry/cli-win32-x64" "2.38.1" -"@sentry/core@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.40.0.tgz#cb5c02d12e29070bf88692c64cfd7db7700be4ea" - integrity sha512-u/U2CJpG/+SmTR2bPM4ZZoPYTJAOUuxzj/0IURnvI0v9+rNu939J/fzrO9huA5IJVxS5TiYykhQm7o6I3Zuo3Q== +"@sentry/core@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.41.0.tgz#e8a25cacd25fe4358f3179e85f3c2697427fe5a6" + integrity sha512-3v7u3t4LozCA5SpZY4yqUN2U3jSrkXNoLgz6L2SUUiydyCuSwXZIFEwpLJfgQyidpNDifeQbBI5E1O910XkPsA== dependencies: - "@sentry/types" "8.40.0" + "@sentry/types" "8.41.0" "@sentry/react@^8.0.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.40.0.tgz#9dfbbeaaeb8731103292d771c52b3d06b7e1500b" - integrity sha512-Ohq/po83r9sh/DCO6VAxx4xU+1ztvFzmXTl3fUnAEc+2bFJK1MsRt6BWfG37XxjQN//mfmyS9KEBgsOpOyd4LQ== + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.41.0.tgz#f99c700dcbd189661d0552a17fb7b817c30c901d" + integrity sha512-/7LEWDNdICYO5s4ie8ztgpmD/GRJ1+1nHlSKvcwjf83COzT1eGvVeuYTiXFAPmXA29sY+lV1RajziwgySadjIQ== dependencies: - "@sentry/browser" "8.40.0" - "@sentry/core" "8.40.0" - "@sentry/types" "8.40.0" + "@sentry/browser" "8.41.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" hoist-non-react-statics "^3.3.2" -"@sentry/types@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.40.0.tgz#a98d2bcc48adbc066b403713688ded3ac5eb1cec" - integrity sha512-nuCf3U3deolPM9BjNnwCc33UtFl9ec15/r74ngAkNccn+A2JXdIAsDkGJMO/9mgSFykLe1QyeJ0pQFRisCGOiA== +"@sentry/types@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.41.0.tgz#db40c93bcedad26569c5dfe10a4b31253c349a10" + integrity sha512-eqdnGr9k9H++b9CjVUoTNUVahPVWeNnMy0YGkqS5+cjWWC+x43p56202oidGFmWo6702ub/xwUNH6M5PC4kq6A== "@sentry/vite-plugin@^2.0.0": version "2.22.6" @@ -3059,9 +3052,9 @@ undici-types "~6.19.8" "@types/node@^20.0.0": - version "20.17.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.7.tgz#790151a28c5a172773d95d53a0c23d3c59a883c4" - integrity sha512-sZXXnpBFMKbao30dUAvzKbdwA2JM1fwUtVEq/kxKuPI5mMwZiRElCpTXb0Biq/LMEVpXDZL5G5V0RPnxKeyaYg== + version "20.17.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.9.tgz#5f141d4b7ee125cdee5faefe28de095398865bab" + integrity sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw== dependencies: undici-types "~6.19.2" @@ -3162,15 +3155,15 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^8.0.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz#c95c6521e70c8b095a684d884d96c0c1c63747d2" - integrity sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg== + version "8.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz#ac56825bcdf3b392fc76a94b1315d4a162f201a6" + integrity sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.15.0" - "@typescript-eslint/type-utils" "8.15.0" - "@typescript-eslint/utils" "8.15.0" - "@typescript-eslint/visitor-keys" "8.15.0" + "@typescript-eslint/scope-manager" "8.16.0" + "@typescript-eslint/type-utils" "8.16.0" + "@typescript-eslint/utils" "8.16.0" + "@typescript-eslint/visitor-keys" "8.16.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" @@ -3184,14 +3177,14 @@ "@typescript-eslint/utils" "5.62.0" "@typescript-eslint/parser@^8.0.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.15.0.tgz#92610da2b3af702cfbc02a46e2a2daa6260a9045" - integrity sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A== + version "8.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.16.0.tgz#ee5b2d6241c1ab3e2e53f03fd5a32d8e266d8e06" + integrity sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w== dependencies: - "@typescript-eslint/scope-manager" "8.15.0" - "@typescript-eslint/types" "8.15.0" - "@typescript-eslint/typescript-estree" "8.15.0" - "@typescript-eslint/visitor-keys" "8.15.0" + "@typescript-eslint/scope-manager" "8.16.0" + "@typescript-eslint/types" "8.16.0" + "@typescript-eslint/typescript-estree" "8.16.0" + "@typescript-eslint/visitor-keys" "8.16.0" debug "^4.3.4" "@typescript-eslint/scope-manager@5.62.0": @@ -3202,21 +3195,21 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz#28a1a0f13038f382424f45a988961acaca38f7c6" - integrity sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA== +"@typescript-eslint/scope-manager@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz#ebc9a3b399a69a6052f3d88174456dd399ef5905" + integrity sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg== dependencies: - "@typescript-eslint/types" "8.15.0" - "@typescript-eslint/visitor-keys" "8.15.0" + "@typescript-eslint/types" "8.16.0" + "@typescript-eslint/visitor-keys" "8.16.0" -"@typescript-eslint/type-utils@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz#a6da0f93aef879a68cc66c73fe42256cb7426c72" - integrity sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw== +"@typescript-eslint/type-utils@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz#585388735f7ac390f07c885845c3d185d1b64740" + integrity sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg== dependencies: - "@typescript-eslint/typescript-estree" "8.15.0" - "@typescript-eslint/utils" "8.15.0" + "@typescript-eslint/typescript-estree" "8.16.0" + "@typescript-eslint/utils" "8.16.0" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -3225,10 +3218,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.15.0.tgz#4958edf3d83e97f77005f794452e595aaf6430fc" - integrity sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ== +"@typescript-eslint/types@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.16.0.tgz#49c92ae1b57942458ab83d9ec7ccab3005e64737" + integrity sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -3243,13 +3236,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz#915c94e387892b114a2a2cc0df2d7f19412c8ba7" - integrity sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg== +"@typescript-eslint/typescript-estree@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz#9d741e56e5b13469b5190e763432ce5551a9300c" + integrity sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw== dependencies: - "@typescript-eslint/types" "8.15.0" - "@typescript-eslint/visitor-keys" "8.15.0" + "@typescript-eslint/types" "8.16.0" + "@typescript-eslint/visitor-keys" "8.16.0" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -3271,15 +3264,15 @@ eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/utils@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.15.0.tgz#ac04679ad19252776b38b81954b8e5a65567cef6" - integrity sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ== +"@typescript-eslint/utils@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.16.0.tgz#c71264c437157feaa97842809836254a6fc833c3" + integrity sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.15.0" - "@typescript-eslint/types" "8.15.0" - "@typescript-eslint/typescript-estree" "8.15.0" + "@typescript-eslint/scope-manager" "8.16.0" + "@typescript-eslint/types" "8.16.0" + "@typescript-eslint/typescript-estree" "8.16.0" "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" @@ -3289,12 +3282,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz#9ea5a85eb25401d2aa74ec8a478af4e97899ea12" - integrity sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q== +"@typescript-eslint/visitor-keys@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz#d5086afc060b01ff7a4ecab8d49d13d5a7b07705" + integrity sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ== dependencies: - "@typescript-eslint/types" "8.15.0" + "@typescript-eslint/types" "8.16.0" eslint-visitor-keys "^4.2.0" "@ungap/structured-clone@^1.2.0": @@ -3338,25 +3331,25 @@ vaul "^1.0.0" "@vitejs/plugin-basic-ssl@^1.0.1": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz#8b840305a6b48e8764803435ec0c716fa27d3802" - integrity sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz#9490fe15b8833351982fbe0963987f69f40f5019" + integrity sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q== "@vitejs/plugin-react@^4.0.1": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz#28301ac6d7aaf20b73a418ee5c65b05519b4836c" - integrity sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA== + version "4.3.4" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz#c64be10b54c4640135a5b28a2432330e88ad7c20" + integrity sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug== dependencies: - "@babel/core" "^7.25.2" - "@babel/plugin-transform-react-jsx-self" "^7.24.7" - "@babel/plugin-transform-react-jsx-source" "^7.24.7" + "@babel/core" "^7.26.0" + "@babel/plugin-transform-react-jsx-self" "^7.25.9" + "@babel/plugin-transform-react-jsx-source" "^7.25.9" "@types/babel__core" "^7.20.5" react-refresh "^0.14.2" "@vitest/coverage-v8@^2.0.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz#74ef3bf6737f9897a54af22f820d90e85883ff83" - integrity sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw== + version "2.1.7" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.7.tgz#63a3d246b6e516ae0746e40e5fe7feaec305a777" + integrity sha512-deQ4J+yu6nEjmEfcBndbgrRM95IZoRpV1dDVRbZhjUcgYVZz/Wc4YaLiDDt9Sy5qcikrJUZMlrUxDy7dBojebg== dependencies: "@ampproject/remapping" "^2.3.0" "@bcoe/v8-coverage" "^0.2.3" @@ -3371,62 +3364,62 @@ test-exclude "^7.0.1" tinyrainbow "^1.2.0" -"@vitest/expect@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.5.tgz#5a6afa6314cae7a61847927bb5bc038212ca7381" - integrity sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q== +"@vitest/expect@2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.7.tgz#5ada3ec9f3060821c8f57880017a7cfb151a259b" + integrity sha512-folWk4qQDEedgUyvaZw94LIJuNLoDtY+rhKhhNy0csdwifn/pQz8EWVRnyrW3j0wMpy+xwJT8WiwiYxk+i+s7w== dependencies: - "@vitest/spy" "2.1.5" - "@vitest/utils" "2.1.5" + "@vitest/spy" "2.1.7" + "@vitest/utils" "2.1.7" chai "^5.1.2" tinyrainbow "^1.2.0" -"@vitest/mocker@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.5.tgz#54ee50648bc0bb606dfc58e13edfacb8b9208324" - integrity sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ== +"@vitest/mocker@2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.7.tgz#424d40edd53b1c25ca00cd5c7cb335b8dcbd82d1" + integrity sha512-nKMTnuJrarFH+7llWxeLmYRldIwTY3OM1DzdytHj0f2+fah6Cyk4XbswhjOiTCnAvXsZAEoo1OaD6rneSSU+3Q== dependencies: - "@vitest/spy" "2.1.5" + "@vitest/spy" "2.1.7" estree-walker "^3.0.3" magic-string "^0.30.12" -"@vitest/pretty-format@2.1.5", "@vitest/pretty-format@^2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.5.tgz#bc79b8826d4a63dc04f2a75d2944694039fa50aa" - integrity sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw== +"@vitest/pretty-format@2.1.7", "@vitest/pretty-format@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.7.tgz#e8549976fbe5672942468f5d6aa4615666fd67de" + integrity sha512-HoqRIyfQlXPrRDB43h0lC8eHPUDPwFweMaD6t+psOvwClCC+oZZim6wPMjuoMnRdiFxXqbybg/QbuewgTwK1vA== dependencies: tinyrainbow "^1.2.0" -"@vitest/runner@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.5.tgz#4d5e2ba2dfc0af74e4b0f9f3f8be020559b26ea9" - integrity sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g== +"@vitest/runner@2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.7.tgz#83c56271adccac6779aca46c5f685df4416f38ce" + integrity sha512-MrDNpXUIXksR57qipYh068SOX4N1hVw6oVILlTlfeTyA1rp0asuljyp15IZwKqhjpWLObFj+tiNrOM4R8UnSqg== dependencies: - "@vitest/utils" "2.1.5" + "@vitest/utils" "2.1.7" pathe "^1.1.2" -"@vitest/snapshot@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.5.tgz#a09a8712547452a84e08b3ec97b270d9cc156b4f" - integrity sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg== +"@vitest/snapshot@2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.7.tgz#5f7afa9e5fd5c8444e376ccece31def1cbc8ec0c" + integrity sha512-OioIxV/xS393DKdlkRNhmtY0K37qVdCv8w1M2SlLTBSX+fNK6zgcd01VlT1nXdbKVDaB8Zb6BOfQYYoGeGTEGg== dependencies: - "@vitest/pretty-format" "2.1.5" + "@vitest/pretty-format" "2.1.7" magic-string "^0.30.12" pathe "^1.1.2" -"@vitest/spy@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.5.tgz#f790d1394a5030644217ce73562e92465e83147e" - integrity sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw== +"@vitest/spy@2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.7.tgz#0d2a8fbdf6e9e75282fa764348e50cd48019414b" + integrity sha512-e5pzIaIC0LBrb/j1FaF7HXlPJLGtltiAkwXTMqNEHALJc7USSLEwziJ+aIWTmjsWNg89zazg37h7oZITnublsQ== dependencies: tinyspy "^3.0.2" -"@vitest/utils@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.5.tgz#0e19ce677c870830a1573d33ee86b0d6109e9546" - integrity sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg== +"@vitest/utils@2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.7.tgz#e9ca17fcba9b3aaaf74c4e815a4148def585bfd2" + integrity sha512-7gUdvIzCCuIrMZu0WHTvDJo8C1NsUtOqmwmcS3bRHUcfHemj29wmkzLVNuWQD7WHoBD/+I7WIgrnzt7kxR54ow== dependencies: - "@vitest/pretty-format" "2.1.5" + "@vitest/pretty-format" "2.1.7" loupe "^3.1.2" tinyrainbow "^1.2.0" @@ -3902,9 +3895,9 @@ caniuse-lite@^1.0.30001646: integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== caniuse-lite@^1.0.30001669: - version "1.0.30001684" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz#0eca437bab7d5f03452ff0ef9de8299be6b08e16" - integrity sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ== + version "1.0.30001685" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001685.tgz#2d10d36c540a9a5d47ad6ab9e1ed5f61fdeadd8c" + integrity sha512-e/kJN1EMyHQzgcMEEgoo+YTCO1NGCmIYHk5Qk8jT6AazWemS5QFKJ5ShCJlH3GZrNIdZofcNCEwZqbMjjKzmnA== caseless@~0.12.0: version "0.12.0" @@ -4171,9 +4164,9 @@ cosmiconfig@^8.1.3: path-type "^4.0.0" cross-spawn@^7.0.0: - version "7.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.5.tgz#910aac880ff5243da96b728bc6521a5f6c2f2f82" - integrity sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -4487,9 +4480,9 @@ easy-table@1.2.0: wcwidth "^1.0.1" electron-to-chromium@^1.5.41: - version "1.5.64" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz#ac8c4c89075d35a1514b620f47dfe48a71ec3697" - integrity sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ== + version "1.5.67" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz#66ebd2be4a77469ac2760ef5e9e460ba9a43a845" + integrity sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ== emoji-regex@^8.0.0: version "8.0.0" @@ -5918,9 +5911,9 @@ jackspeak@^3.1.2: "@pkgjs/parseargs" "^0.11.0" jiti@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.0.tgz#393d595fb6031a11d11171b5e4fc0b989ba3e053" - integrity sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g== + version "2.4.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.1.tgz#4de9766ccbfa941d9b6390d2b159a4b295a52e6b" + integrity sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -6047,9 +6040,9 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== knip@^5.27.2: - version "5.37.2" - resolved "https://registry.yarnpkg.com/knip/-/knip-5.37.2.tgz#e218afae3bf28ec10fa3be419cf6d89fd20fd63b" - integrity sha512-Rs9HHTgmUacyKxchP4kRwG8idi0tzVHVpSyo4EM9sNGDSrPq20lhKXOWMFmShGCV6CH2352393Ok/qG1NblCMw== + version "5.38.4" + resolved "https://registry.yarnpkg.com/knip/-/knip-5.38.4.tgz#cfc316c4f0fcf9224400d28229aad608e7118f68" + integrity sha512-7YyB9nhnmYhaxB/LbfBUmU67reAnnudNxTl6oBF7McB11TlfoOOgjrakZHOw0bp2ucUtyxtplhBcl7O++715nA== dependencies: "@nodelib/fs.walk" "1.2.8" "@snyk/github-codeowners" "1.1.0" @@ -6062,7 +6055,7 @@ knip@^5.27.2: picocolors "^1.1.0" picomatch "^4.0.1" pretty-ms "^9.0.0" - smol-toml "^1.3.0" + smol-toml "^1.3.1" strip-json-comments "5.0.1" summary "2.1.0" zod "^3.22.4" @@ -6211,9 +6204,9 @@ magic-string@0.30.8: "@jridgewell/sourcemap-codec" "^1.4.15" magic-string@^0.30.12: - version "0.30.12" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" - integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw== + version "0.30.14" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.14.tgz#e9bb29870b81cfc1ec3cc656552f5a7fcbf19077" + integrity sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw== dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" @@ -7015,9 +7008,9 @@ prelude-ls@^1.2.1: integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@^3.0.0, prettier@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" - integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + version "3.4.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.1.tgz#e211d451d6452db0a291672ca9154bc8c2579f7b" + integrity sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg== pretty-format@^27.0.2: version "27.5.1" @@ -7139,9 +7132,9 @@ react-error-boundary@^3.1.0: "@babel/runtime" "^7.12.5" react-i18next@^15.0.0: - version "15.1.1" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.1.1.tgz#30bc76b39ded6ee37f1457677e46e6d6f11d9f64" - integrity sha512-R/Vg9wIli2P3FfeI8o1eNJUJue5LWpFsQePCHdQDmX0Co3zkr6kdT8gAseb/yGeWbNz1Txc4bKDQuZYsC0kQfw== + version "15.1.3" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.1.3.tgz#172c3905038ea4f90699a19949a0084b5641c94f" + integrity sha512-J11oA30FbM3NZegUZjn8ySK903z6PLBz/ZuBYyT1JMR0QPrW6PFXvl1WoUhortdGi9dM0m48/zJQlPskVZXgVw== dependencies: "@babel/runtime" "^7.25.0" html-parse-stringify "^3.0.1" @@ -7709,7 +7702,7 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -smol-toml@^1.3.0: +smol-toml@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/smol-toml/-/smol-toml-1.3.1.tgz#d9084a9e212142e3cab27ef4e2b8e8ba620bfe15" integrity sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ== @@ -8109,9 +8102,9 @@ tr46@~0.0.3: integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== ts-api-utils@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.1.tgz#7c0a304cd446d9a497c24c960b8abbf0bc1611ae" - integrity sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w== + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== ts-debounce@^4.0.0: version "4.0.0" @@ -8475,10 +8468,10 @@ vinyl@^3.0.0, vinyl@~3.0.0: replace-ext "^2.0.0" teex "^1.0.1" -vite-node@2.1.5: - version "2.1.5" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.5.tgz#cf28c637b2ebe65921f3118a165b7cf00a1cdf19" - integrity sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w== +vite-node@2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.7.tgz#58bc9babc570ddf9cfbb2272d6d8d73ca28d139d" + integrity sha512-b/5MxSWd0ftWt1B1LHfzCw0ASzaxHztUwP0rcsBhkDSGy9ZDEDieSIjFG3I78nI9dUN0eSeD6LtuKPZGjwwpZQ== dependencies: cac "^6.7.14" debug "^4.3.7" @@ -8487,14 +8480,12 @@ vite-node@2.1.5: vite "^5.0.0" vite-plugin-compression2@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/vite-plugin-compression2/-/vite-plugin-compression2-1.3.1.tgz#ac2a512f8ca90a76687add6cf441000dd2c41485" - integrity sha512-UMr66CFu+RVPiD8E3iaX9BdZjCgO+lzzaAPAZvL5YgwH6FU4OR/MulJEyp9wq9EKoO6ErjUtPpaiDi3hvzv79Q== + version "1.3.3" + resolved "https://registry.yarnpkg.com/vite-plugin-compression2/-/vite-plugin-compression2-1.3.3.tgz#d33ddfb4000c914783f4760f81a44ba52fc21ed1" + integrity sha512-Mb+xi/C5b68awtF4fNwRBPtoZiyUHU3I0SaBOAGlerlR31kusq1si6qG31lsjJH8T7QNg/p3IJY2HY9O9SvsfQ== dependencies: "@rollup/pluginutils" "^5.1.0" tar-mini "^0.2.0" - optionalDependencies: - vite "^5.3.4" vite-plugin-html-template@^1.1.0: version "1.2.2" @@ -8512,7 +8503,7 @@ vite-plugin-svgr@^4.0.0: "@svgr/core" "^8.1.0" "@svgr/plugin-jsx" "^8.1.0" -vite@^5.0.0, vite@^5.3.4: +vite@^5.0.0: version "5.4.11" resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q== @@ -8533,17 +8524,17 @@ vitest-axe@^1.0.0-pre.3: lodash-es "^4.17.21" vitest@^2.0.0: - version "2.1.5" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.5.tgz#a93b7b84a84650130727baae441354e6df118148" - integrity sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A== + version "2.1.7" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.7.tgz#a24c8fb6e54e195fc51057740c1e4c4afd96fff5" + integrity sha512-wzJ7Wri44ufkzTZbI1lHsdHfiGdFRmnJ9qIudDQ6tknjJeHhF5QgNSSjk7KRZUU535qEiEXFJ7tSHqyzyIv0jQ== dependencies: - "@vitest/expect" "2.1.5" - "@vitest/mocker" "2.1.5" - "@vitest/pretty-format" "^2.1.5" - "@vitest/runner" "2.1.5" - "@vitest/snapshot" "2.1.5" - "@vitest/spy" "2.1.5" - "@vitest/utils" "2.1.5" + "@vitest/expect" "2.1.7" + "@vitest/mocker" "2.1.7" + "@vitest/pretty-format" "^2.1.7" + "@vitest/runner" "2.1.7" + "@vitest/snapshot" "2.1.7" + "@vitest/spy" "2.1.7" + "@vitest/utils" "2.1.7" chai "^5.1.2" debug "^4.3.7" expect-type "^1.1.0" @@ -8555,7 +8546,7 @@ vitest@^2.0.0: tinypool "^1.0.1" tinyrainbow "^1.2.0" vite "^5.0.0" - vite-node "2.1.5" + vite-node "2.1.7" why-is-node-running "^2.3.0" void-elements@3.1.0: From cc58e4d4293323be78c5cb6b21fd6ca130e1af65 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:11:39 +0000 Subject: [PATCH 12/72] Update dependency vite to v6 --- package.json | 2 +- yarn.lock | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 280 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ebaa59ff..25a2ac80 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "typescript-eslint-language-service": "^5.0.5", "unique-names-generator": "^4.6.0", "vaul": "^1.0.0", - "vite": "^5.0.0", + "vite": "^6.0.0", "vite-plugin-compression2": "^1.3.1", "vite-plugin-html-template": "^1.1.0", "vite-plugin-svgr": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 5b75ebb0..88167966 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1338,6 +1338,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz#145b74d5e4a5223489cabdc238d8dad902df5259" integrity sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ== +"@esbuild/aix-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" + integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== + "@esbuild/android-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" @@ -1348,6 +1353,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz#453bbe079fc8d364d4c5545069e8260228559832" integrity sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ== +"@esbuild/android-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" + integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== + "@esbuild/android-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" @@ -1358,6 +1368,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.0.tgz#26c806853aa4a4f7e683e519cd9d68e201ebcf99" integrity sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g== +"@esbuild/android-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" + integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== + "@esbuild/android-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" @@ -1368,6 +1383,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.0.tgz#1e51af9a6ac1f7143769f7ee58df5b274ed202e6" integrity sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ== +"@esbuild/android-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" + integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== + "@esbuild/darwin-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" @@ -1378,6 +1398,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz#d996187a606c9534173ebd78c58098a44dd7ef9e" integrity sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow== +"@esbuild/darwin-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" + integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== + "@esbuild/darwin-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" @@ -1388,6 +1413,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz#30c8f28a7ef4e32fe46501434ebe6b0912e9e86c" integrity sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ== +"@esbuild/darwin-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" + integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== + "@esbuild/freebsd-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" @@ -1398,6 +1428,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz#30f4fcec8167c08a6e8af9fc14b66152232e7fb4" integrity sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw== +"@esbuild/freebsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" + integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== + "@esbuild/freebsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" @@ -1408,6 +1443,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz#1003a6668fe1f5d4439e6813e5b09a92981bc79d" integrity sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ== +"@esbuild/freebsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" + integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== + "@esbuild/linux-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" @@ -1418,6 +1458,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz#3b9a56abfb1410bb6c9138790f062587df3e6e3a" integrity sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw== +"@esbuild/linux-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" + integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== + "@esbuild/linux-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" @@ -1428,6 +1473,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz#237a8548e3da2c48cd79ae339a588f03d1889aad" integrity sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw== +"@esbuild/linux-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" + integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== + "@esbuild/linux-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" @@ -1438,6 +1488,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz#4269cd19cb2de5de03a7ccfc8855dde3d284a238" integrity sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA== +"@esbuild/linux-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" + integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== + "@esbuild/linux-loong64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" @@ -1448,6 +1503,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz#82b568f5658a52580827cc891cb69d2cb4f86280" integrity sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A== +"@esbuild/linux-loong64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" + integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== + "@esbuild/linux-mips64el@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" @@ -1458,6 +1518,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz#9a57386c926262ae9861c929a6023ed9d43f73e5" integrity sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w== +"@esbuild/linux-mips64el@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" + integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== + "@esbuild/linux-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" @@ -1468,6 +1533,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz#f3a79fd636ba0c82285d227eb20ed8e31b4444f6" integrity sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw== +"@esbuild/linux-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" + integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== + "@esbuild/linux-riscv64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" @@ -1478,6 +1548,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz#f9d2ef8356ce6ce140f76029680558126b74c780" integrity sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw== +"@esbuild/linux-riscv64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" + integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== + "@esbuild/linux-s390x@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" @@ -1488,6 +1563,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz#45390f12e802201f38a0229e216a6aed4351dfe8" integrity sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg== +"@esbuild/linux-s390x@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" + integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== + "@esbuild/linux-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" @@ -1498,6 +1578,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz#c8409761996e3f6db29abcf9b05bee8d7d80e910" integrity sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ== +"@esbuild/linux-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" + integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== + "@esbuild/netbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" @@ -1508,11 +1593,21 @@ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz#ba70db0114380d5f6cfb9003f1d378ce989cd65c" integrity sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw== +"@esbuild/netbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" + integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== + "@esbuild/openbsd-arm64@0.23.0": version "0.23.0" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz#72fc55f0b189f7a882e3cf23f332370d69dfd5db" integrity sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ== +"@esbuild/openbsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" + integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== + "@esbuild/openbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" @@ -1523,6 +1618,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz#b6ae7a0911c18fe30da3db1d6d17a497a550e5d8" integrity sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg== +"@esbuild/openbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" + integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== + "@esbuild/sunos-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" @@ -1533,6 +1633,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz#58f0d5e55b9b21a086bfafaa29f62a3eb3470ad8" integrity sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA== +"@esbuild/sunos-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" + integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== + "@esbuild/win32-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" @@ -1543,6 +1648,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz#b858b2432edfad62e945d5c7c9e5ddd0f528ca6d" integrity sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ== +"@esbuild/win32-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" + integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== + "@esbuild/win32-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" @@ -1553,6 +1663,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz#167ef6ca22a476c6c0c014a58b4f43ae4b80dec7" integrity sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA== +"@esbuild/win32-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" + integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== + "@esbuild/win32-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" @@ -1563,6 +1678,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz#db44a6a08520b5f25bbe409f34a59f2d4bcc7ced" integrity sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g== +"@esbuild/win32-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" + integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -2560,91 +2680,181 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz#3e7eda4c0c1de6d2415343002d742ff95e38dca7" integrity sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA== +"@rollup/rollup-android-arm-eabi@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.0.tgz#462e7ecdd60968bc9eb95a20d185e74f8243ec1b" + integrity sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ== + "@rollup/rollup-android-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz#04f679231acf7284f1f8a1f7250d0e0944865ba8" integrity sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg== +"@rollup/rollup-android-arm64@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.0.tgz#78a2b8a8a55f71a295eb860a654ae90a2b168f40" + integrity sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA== + "@rollup/rollup-darwin-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz#ecea723041621747d0772af93b54752edf26467a" integrity sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg== +"@rollup/rollup-darwin-arm64@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.0.tgz#5b783af714f434f1e66e3cdfa3817e0b99216d84" + integrity sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q== + "@rollup/rollup-darwin-x64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz#28e6e0687092f31e20982fc104779d48c643fc21" integrity sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA== +"@rollup/rollup-darwin-x64@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.0.tgz#f72484e842521a5261978034e18e20f778a2850d" + integrity sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w== + "@rollup/rollup-freebsd-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz#99e9173b8aef3d1ef086983da70413988206e530" integrity sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g== +"@rollup/rollup-freebsd-arm64@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.0.tgz#3c919dff72b2fe344811a609c674a8347b033f62" + integrity sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ== + "@rollup/rollup-freebsd-x64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz#f3a1ef941f8d3c6b2b036484c69a7b2d3d9ebbd7" integrity sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw== +"@rollup/rollup-freebsd-x64@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.0.tgz#b62a3a8365b363b3fdfa6da11a9188b6ab4dca7c" + integrity sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA== + "@rollup/rollup-linux-arm-gnueabihf@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz#9ba6adcc33f26f2a0c6ee658f0bbda4de8da2f75" integrity sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA== +"@rollup/rollup-linux-arm-gnueabihf@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.0.tgz#0d02cc55bd229bd8ca5c54f65f916ba5e0591c94" + integrity sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w== + "@rollup/rollup-linux-arm-musleabihf@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz#62f2426fa9016ec884f4fa779d7b62d5ba02a41a" integrity sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ== +"@rollup/rollup-linux-arm-musleabihf@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.0.tgz#c51d379263201e88a60e92bd8e90878f0c044425" + integrity sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg== + "@rollup/rollup-linux-arm64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz#f98ec111a231d35e0c6d3404e3d80f67f9d5b9f8" integrity sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A== +"@rollup/rollup-linux-arm64-gnu@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.0.tgz#93ce2addc337b5cfa52b84f8e730d2e36eb4339b" + integrity sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg== + "@rollup/rollup-linux-arm64-musl@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz#4b36ffb8359f959f2c29afd187603c53368b6723" integrity sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw== +"@rollup/rollup-linux-arm64-musl@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.0.tgz#730af6ddc091a5ba5baac28a3510691725dc808b" + integrity sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw== + "@rollup/rollup-linux-powerpc64le-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz#52f4b39e6783505d168a745b79d86474fde71680" integrity sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA== +"@rollup/rollup-linux-powerpc64le-gnu@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.0.tgz#b5565aac20b4de60ca1e557f525e76478b5436af" + integrity sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ== + "@rollup/rollup-linux-riscv64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz#49195be7e6a7d68d482b12461e2ea914e31ff977" integrity sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA== +"@rollup/rollup-linux-riscv64-gnu@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.0.tgz#d488290bf9338bad4ae9409c4aa8a1728835a20b" + integrity sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g== + "@rollup/rollup-linux-s390x-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz#4b8d50a205eac7b46cdcb9c50d4a6ae5994c02e0" integrity sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ== +"@rollup/rollup-linux-s390x-gnu@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.0.tgz#eb2e3f3a06acf448115045c11a5a96868c95a556" + integrity sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw== + "@rollup/rollup-linux-x64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz#dfcceebc5ccac7fc2db19471996026258c81b55f" integrity sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig== +"@rollup/rollup-linux-x64-gnu@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz#065952ef2aea7e837dc7e02aa500feeaff4fc507" + integrity sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw== + "@rollup/rollup-linux-x64-musl@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz#192f78bad8429711d63a31dc0a7d3312e2df850e" integrity sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ== +"@rollup/rollup-linux-x64-musl@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.0.tgz#3435d484d05f5c4d1ffd54541b4facce2887103a" + integrity sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw== + "@rollup/rollup-win32-arm64-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz#f4ec076579634f780b4e5896ae7f59f3e38e0c60" integrity sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww== +"@rollup/rollup-win32-arm64-msvc@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.0.tgz#69682a2a10d9fedc334f87583cfca83c39c08077" + integrity sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg== + "@rollup/rollup-win32-ia32-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz#5458eab1929827e4f805cefb90bd09ecf7eeed2b" integrity sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg== +"@rollup/rollup-win32-ia32-msvc@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.0.tgz#b64470f9ac79abb386829c56750b9a4711be3332" + integrity sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A== + "@rollup/rollup-win32-x64-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz#93415e7e707e4b156d77c5950b983b58f4bc33f3" integrity sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg== +"@rollup/rollup-win32-x64-msvc@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.0.tgz#cb313feef9ac6e3737067fdf34f42804ac65a6f2" + integrity sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ== + "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" @@ -4712,6 +4922,36 @@ esbuild@^0.23.0: "@esbuild/win32-ia32" "0.23.0" "@esbuild/win32-x64" "0.23.0" +esbuild@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.0.tgz#f2d470596885fcb2e91c21eb3da3b3c89c0b55e7" + integrity sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.24.0" + "@esbuild/android-arm" "0.24.0" + "@esbuild/android-arm64" "0.24.0" + "@esbuild/android-x64" "0.24.0" + "@esbuild/darwin-arm64" "0.24.0" + "@esbuild/darwin-x64" "0.24.0" + "@esbuild/freebsd-arm64" "0.24.0" + "@esbuild/freebsd-x64" "0.24.0" + "@esbuild/linux-arm" "0.24.0" + "@esbuild/linux-arm64" "0.24.0" + "@esbuild/linux-ia32" "0.24.0" + "@esbuild/linux-loong64" "0.24.0" + "@esbuild/linux-mips64el" "0.24.0" + "@esbuild/linux-ppc64" "0.24.0" + "@esbuild/linux-riscv64" "0.24.0" + "@esbuild/linux-s390x" "0.24.0" + "@esbuild/linux-x64" "0.24.0" + "@esbuild/netbsd-x64" "0.24.0" + "@esbuild/openbsd-arm64" "0.24.0" + "@esbuild/openbsd-x64" "0.24.0" + "@esbuild/sunos-x64" "0.24.0" + "@esbuild/win32-arm64" "0.24.0" + "@esbuild/win32-ia32" "0.24.0" + "@esbuild/win32-x64" "0.24.0" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -6979,7 +7219,7 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.41, postcss@^8.4.43: +postcss@^8.4.41, postcss@^8.4.43, postcss@^8.4.49: version "8.4.49" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== @@ -7498,6 +7738,33 @@ rollup@^4.20.0: "@rollup/rollup-win32-x64-msvc" "4.25.0" fsevents "~2.3.2" +rollup@^4.23.0: + version "4.28.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.28.0.tgz#eb8d28ed43ef60a18f21d0734d230ee79dd0de77" + integrity sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.28.0" + "@rollup/rollup-android-arm64" "4.28.0" + "@rollup/rollup-darwin-arm64" "4.28.0" + "@rollup/rollup-darwin-x64" "4.28.0" + "@rollup/rollup-freebsd-arm64" "4.28.0" + "@rollup/rollup-freebsd-x64" "4.28.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.28.0" + "@rollup/rollup-linux-arm-musleabihf" "4.28.0" + "@rollup/rollup-linux-arm64-gnu" "4.28.0" + "@rollup/rollup-linux-arm64-musl" "4.28.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.28.0" + "@rollup/rollup-linux-riscv64-gnu" "4.28.0" + "@rollup/rollup-linux-s390x-gnu" "4.28.0" + "@rollup/rollup-linux-x64-gnu" "4.28.0" + "@rollup/rollup-linux-x64-musl" "4.28.0" + "@rollup/rollup-win32-arm64-msvc" "4.28.0" + "@rollup/rollup-win32-ia32-msvc" "4.28.0" + "@rollup/rollup-win32-x64-msvc" "4.28.0" + fsevents "~2.3.2" + rrweb-cssom@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" @@ -8514,6 +8781,17 @@ vite@^5.0.0: optionalDependencies: fsevents "~2.3.3" +vite@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.2.tgz#7a22630c73c7b663335ddcdb2390971ffbc14993" + integrity sha512-XdQ+VsY2tJpBsKGs0wf3U/+azx8BBpYRHFAyKm5VeEZNOJZRB63q7Sc8Iup3k0TrN3KO6QgyzFf+opSbfY1y0g== + dependencies: + esbuild "^0.24.0" + postcss "^8.4.49" + rollup "^4.23.0" + optionalDependencies: + fsevents "~2.3.3" + vitest-axe@^1.0.0-pre.3: version "1.0.0-pre.3" resolved "https://registry.yarnpkg.com/vitest-axe/-/vitest-axe-1.0.0-pre.3.tgz#0ea646c4ebe21c9b7ffb9ff3d6dff60b1c5a6124" From ea6f2952af8ad576b996d773a4556511167dddc0 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 2 Dec 2024 15:16:58 +0000 Subject: [PATCH 13/72] Add sound effect for call joined / left (#2794) * Add renderer for call joined / left * lint * Add new sounds * Updates sounds in renderer * lint * move import * pad sounds with silence * lint * tidy * Drop autoplay since we now subscribe correctly. * Comitting test files I am going to be going to lunch so will tidy up in a little while. * finish up tests * Add support for multiple channels per sound. * lint --- src/room/CallEventAudioRenderer.test.tsx | 225 +++++++++++++++++++++++ src/room/CallEventAudioRenderer.tsx | 117 ++++++++++++ src/room/InCallView.tsx | 2 + src/room/ReactionAudioRenderer.test.tsx | 19 +- src/sound/LICENCE.md | 2 + src/sound/join_call.mp3 | Bin 0 -> 11605 bytes src/sound/join_call.ogg | Bin 0 -> 10939 bytes src/sound/left_call.mp3 | Bin 0 -> 11647 bytes src/sound/left_call.ogg | Bin 0 -> 10634 bytes src/state/CallViewModel.ts | 13 ++ src/utils/test.ts | 38 ++++ 11 files changed, 401 insertions(+), 15 deletions(-) create mode 100644 src/room/CallEventAudioRenderer.test.tsx create mode 100644 src/room/CallEventAudioRenderer.tsx create mode 100644 src/sound/join_call.mp3 create mode 100644 src/sound/join_call.ogg create mode 100644 src/sound/left_call.mp3 create mode 100644 src/sound/left_call.ogg diff --git a/src/room/CallEventAudioRenderer.test.tsx b/src/room/CallEventAudioRenderer.test.tsx new file mode 100644 index 00000000..9014e60b --- /dev/null +++ b/src/room/CallEventAudioRenderer.test.tsx @@ -0,0 +1,225 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { render } from "@testing-library/react"; +import { beforeEach, expect, test } from "vitest"; +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { ConnectionState, RemoteParticipant, Room } from "livekit-client"; +import { of } from "rxjs"; +import { afterEach } from "node:test"; +import { act } from "react"; + +import { soundEffectVolumeSetting } from "../settings/settings"; +import { + EmittableMockLivekitRoom, + mockLivekitRoom, + mockLocalParticipant, + mockMatrixRoom, + mockMatrixRoomMember, + mockMediaPlay, + mockRemoteParticipant, +} from "../utils/test"; +import { E2eeType } from "../e2ee/e2eeType"; +import { CallViewModel } from "../state/CallViewModel"; +import { + CallEventAudioRenderer, + MAX_PARTICIPANT_COUNT_FOR_SOUND, +} from "./CallEventAudioRenderer"; + +const alice = mockMatrixRoomMember({ userId: "@alice:example.org" }); +const bob = mockMatrixRoomMember({ userId: "@bob:example.org" }); +const aliceId = `${alice.userId}:AAAA`; +const bobId = `${bob.userId}:BBBB`; +const localParticipant = mockLocalParticipant({ identity: "" }); +const aliceParticipant = mockRemoteParticipant({ identity: aliceId }); +const bobParticipant = mockRemoteParticipant({ identity: bobId }); + +const originalPlayFn = window.HTMLMediaElement.prototype.play; + +const enterSound = "http://localhost:3000/src/sound/join_call.ogg"; +const leaveSound = "http://localhost:3000/src/sound/left_call.ogg"; + +beforeEach(() => { + soundEffectVolumeSetting.setValue(soundEffectVolumeSetting.defaultValue); +}); + +afterEach(() => { + window.HTMLMediaElement.prototype.play = originalPlayFn; +}); + +test("plays a sound when entering a call", () => { + const audioIsPlaying: string[] = mockMediaPlay(); + const members = new Map([alice, bob].map((p) => [p.userId, p])); + const remoteParticipants = of([aliceParticipant]); + const liveKitRoom = mockLivekitRoom( + { localParticipant }, + { remoteParticipants }, + ); + + const vm = new CallViewModel( + mockMatrixRoom({ + client: { + getUserId: () => "@carol:example.org", + } as Partial as MatrixClient, + getMember: (userId) => members.get(userId) ?? null, + }), + liveKitRoom, + { + kind: E2eeType.PER_PARTICIPANT, + }, + of(ConnectionState.Connected), + ); + + render(); + expect(audioIsPlaying).toEqual([ + // Joining the call + enterSound, + ]); +}); + +test("plays no sound when muted", () => { + soundEffectVolumeSetting.setValue(0); + const audioIsPlaying: string[] = mockMediaPlay(); + const members = new Map([alice, bob].map((p) => [p.userId, p])); + const remoteParticipants = of([aliceParticipant, bobParticipant]); + const liveKitRoom = mockLivekitRoom( + { localParticipant }, + { remoteParticipants }, + ); + + const vm = new CallViewModel( + mockMatrixRoom({ + client: { + getUserId: () => "@carol:example.org", + } as Partial as MatrixClient, + getMember: (userId) => members.get(userId) ?? null, + }), + liveKitRoom, + { + kind: E2eeType.PER_PARTICIPANT, + }, + of(ConnectionState.Connected), + ); + + render(); + // Play a sound when joining a call. + expect(audioIsPlaying).toHaveLength(0); +}); + +test("plays a sound when a user joins", () => { + const audioIsPlaying: string[] = mockMediaPlay(); + const members = new Map([alice].map((p) => [p.userId, p])); + const remoteParticipants = new Map( + [aliceParticipant].map((p) => [p.identity, p]), + ); + const liveKitRoom = new EmittableMockLivekitRoom({ + localParticipant, + remoteParticipants, + }); + + const vm = new CallViewModel( + mockMatrixRoom({ + client: { + getUserId: () => "@carol:example.org", + } as Partial as MatrixClient, + getMember: (userId) => members.get(userId) ?? null, + }), + liveKitRoom as unknown as Room, + { + kind: E2eeType.PER_PARTICIPANT, + }, + of(ConnectionState.Connected), + ); + render(); + + act(() => { + liveKitRoom.addParticipant(bobParticipant); + }); + // Play a sound when joining a call. + expect(audioIsPlaying).toEqual([ + // Joining the call + enterSound, + // Bob leaves + enterSound, + ]); +}); + +test("plays a sound when a user leaves", () => { + const audioIsPlaying: string[] = mockMediaPlay(); + const members = new Map([alice].map((p) => [p.userId, p])); + const remoteParticipants = new Map( + [aliceParticipant].map((p) => [p.identity, p]), + ); + const liveKitRoom = new EmittableMockLivekitRoom({ + localParticipant, + remoteParticipants, + }); + + const vm = new CallViewModel( + mockMatrixRoom({ + client: { + getUserId: () => "@carol:example.org", + } as Partial as MatrixClient, + getMember: (userId) => members.get(userId) ?? null, + }), + liveKitRoom as unknown as Room, + { + kind: E2eeType.PER_PARTICIPANT, + }, + of(ConnectionState.Connected), + ); + render(); + + act(() => { + liveKitRoom.removeParticipant(aliceParticipant); + }); + expect(audioIsPlaying).toEqual([ + // Joining the call + enterSound, + // Alice leaves + leaveSound, + ]); +}); + +test("plays no sound when the participant list", () => { + const audioIsPlaying: string[] = mockMediaPlay(); + const members = new Map([alice].map((p) => [p.userId, p])); + const remoteParticipants = new Map([ + [aliceParticipant.identity, aliceParticipant], + ...Array.from({ length: MAX_PARTICIPANT_COUNT_FOR_SOUND - 1 }).map< + [string, RemoteParticipant] + >((_, index) => { + const p = mockRemoteParticipant({ identity: `user${index}` }); + return [p.identity, p]; + }), + ]); + const liveKitRoom = new EmittableMockLivekitRoom({ + localParticipant, + remoteParticipants, + }); + + const vm = new CallViewModel( + mockMatrixRoom({ + client: { + getUserId: () => "@carol:example.org", + } as Partial as MatrixClient, + getMember: (userId) => members.get(userId) ?? null, + }), + liveKitRoom as unknown as Room, + { + kind: E2eeType.PER_PARTICIPANT, + }, + of(ConnectionState.Connected), + ); + render(); + expect(audioIsPlaying).toEqual([]); + // When the count drops + act(() => { + liveKitRoom.removeParticipant(aliceParticipant); + }); + expect(audioIsPlaying).toEqual([leaveSound]); +}); diff --git a/src/room/CallEventAudioRenderer.tsx b/src/room/CallEventAudioRenderer.tsx new file mode 100644 index 00000000..4f8213cf --- /dev/null +++ b/src/room/CallEventAudioRenderer.tsx @@ -0,0 +1,117 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { ReactNode, useEffect, useRef } from "react"; +import { filter } from "rxjs"; + +import { + soundEffectVolumeSetting as effectSoundVolumeSetting, + useSetting, +} from "../settings/settings"; +import { CallViewModel } from "../state/CallViewModel"; +import enterCallSoundMp3 from "../sound/join_call.mp3"; +import enterCallSoundOgg from "../sound/join_call.ogg"; +import leftCallSoundMp3 from "../sound/left_call.mp3"; +import leftCallSoundOgg from "../sound/left_call.ogg"; + +// Do not play any sounds if the participant count has exceeded this +// number. +export const MAX_PARTICIPANT_COUNT_FOR_SOUND = 8; +export const CONCURRENT_AUDIO_CHANNELS = 2; + +export function CallEventAudioRenderer({ + vm, +}: { + vm: CallViewModel; +}): ReactNode { + const [effectSoundVolume] = useSetting(effectSoundVolumeSetting); + const callEntered = useRef<(HTMLAudioElement | null)[]>([]); + const callLeft = useRef<(HTMLAudioElement | null)[]>([]); + + useEffect(() => { + if (effectSoundVolume === 0) { + return; + } + const joinSub = vm.memberChanges + .pipe( + filter( + ({ joined, ids }) => + ids.length <= MAX_PARTICIPANT_COUNT_FOR_SOUND && joined.length > 0, + ), + ) + .subscribe(({ joined }) => { + const availablePlayer = callEntered.current.find((v) => v?.paused); + void availablePlayer?.play(); + }); + + const leftSub = vm.memberChanges + .pipe( + filter( + ({ ids, left }) => + ids.length <= MAX_PARTICIPANT_COUNT_FOR_SOUND && left.length > 0, + ), + ) + .subscribe(() => { + const availablePlayer = callLeft.current.find((v) => v?.paused); + void availablePlayer?.play(); + }); + + return (): void => { + joinSub.unsubscribe(); + leftSub.unsubscribe(); + }; + }, [effectSoundVolume, callEntered, callLeft, vm]); + + // Set volume. + useEffect(() => { + callEntered.current.forEach((a) => { + if (a) { + a.volume = effectSoundVolume; + } + }); + callLeft.current.forEach((a) => { + if (a) { + a.volume = effectSoundVolume; + } + }); + }, [callEntered, callLeft, effectSoundVolume]); + + // Do not render any audio elements if playback is disabled. Will save + // audio file fetches. + if (effectSoundVolume === 0) { + return null; + } + + return ( + // Will play as soon as it's mounted, which is what we want as this will + // play when the call is entered. + <> + {Array.from({ length: CONCURRENT_AUDIO_CHANNELS }).map((_, index) => ( + + ))} + {Array.from({ length: CONCURRENT_AUDIO_CHANNELS }).map((_, index) => ( + + ))} + + ); +} diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index f4340f47..f1afa3e4 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -87,6 +87,7 @@ import { ReactionsAudioRenderer } from "./ReactionAudioRenderer"; import { useSwitchCamera } from "./useSwitchCamera"; import { soundEffectVolumeSetting, useSetting } from "../settings/settings"; import { ReactionsOverlay } from "./ReactionsOverlay"; +import { CallEventAudioRenderer } from "./CallEventAudioRenderer"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); @@ -670,6 +671,7 @@ export const InCallView: FC = ({ ))} {renderContent()} +
-
+
( /> )}
+ {!video && !localParticipant && ( +
+ {t("video_tile.waiting_for_media")} +
+ )} {/* TODO: Bring this back once encryption status is less broken */} {/*encryptionStatus !== EncryptionStatus.Okay && (
diff --git a/src/tile/SpotlightTile.test.tsx b/src/tile/SpotlightTile.test.tsx index cedeea62..29b574a2 100644 --- a/src/tile/SpotlightTile.test.tsx +++ b/src/tile/SpotlightTile.test.tsx @@ -12,7 +12,11 @@ import userEvent from "@testing-library/user-event"; import { of } from "rxjs"; import { SpotlightTile } from "./SpotlightTile"; -import { withLocalMedia, withRemoteMedia } from "../utils/test"; +import { + mockRtcMembership, + withLocalMedia, + withRemoteMedia, +} from "../utils/test"; import { SpotlightTileViewModel } from "../state/TileViewModel"; global.IntersectionObserver = class MockIntersectionObserver { @@ -22,6 +26,7 @@ global.IntersectionObserver = class MockIntersectionObserver { test("SpotlightTile is accessible", async () => { await withRemoteMedia( + mockRtcMembership("@alice:example.org", "AAAA"), { rawDisplayName: "Alice", getMxcAvatarUrl: () => "mxc://adfsg", @@ -29,6 +34,7 @@ test("SpotlightTile is accessible", async () => { {}, async (vm1) => { await withLocalMedia( + mockRtcMembership("@bob:example.org", "BBBB"), { rawDisplayName: "Bob", getMxcAvatarUrl: () => "mxc://dlskf", diff --git a/src/tile/SpotlightTile.tsx b/src/tile/SpotlightTile.tsx index 1c85df92..dce30d5f 100644 --- a/src/tile/SpotlightTile.tsx +++ b/src/tile/SpotlightTile.tsx @@ -49,12 +49,13 @@ interface SpotlightItemBaseProps { "data-id": string; targetWidth: number; targetHeight: number; - video: TrackReferenceOrPlaceholder; + video: TrackReferenceOrPlaceholder | undefined; member: RoomMember | undefined; unencryptedWarning: boolean; encryptionStatus: EncryptionStatus; displayName: string; "aria-hidden"?: boolean; + localParticipant: boolean; } interface SpotlightUserMediaItemBaseProps extends SpotlightItemBaseProps { @@ -163,6 +164,7 @@ const SpotlightItem = forwardRef( displayName, encryptionStatus, "aria-hidden": ariaHidden, + localParticipant: vm.local, }; return vm instanceof ScreenShareViewModel ? ( @@ -210,7 +212,9 @@ export const SpotlightTile = forwardRef( const ref = useMergedRefs(ourRef, theirRef); const maximised = useObservableEagerState(vm.maximised); const media = useObservableEagerState(vm.media); - const [visibleId, setVisibleId] = useState(media[0].id); + const [visibleId, setVisibleId] = useState( + media[0]?.id, + ); const latestMedia = useLatest(media); const latestVisibleId = useLatest(visibleId); const visibleIndex = media.findIndex((vm) => vm.id === visibleId); diff --git a/src/utils/test.ts b/src/utils/test.ts index 99a9264b..dca98825 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -7,7 +7,20 @@ Please see LICENSE in the repository root for full details. import { map, Observable, of, SchedulerLike } from "rxjs"; import { RunHelpers, TestScheduler } from "rxjs/testing"; import { expect, vi } from "vitest"; -import { RoomMember, Room as MatrixRoom } from "matrix-js-sdk/src/matrix"; +import { + RoomMember, + Room as MatrixRoom, + MatrixEvent, + Room, + TypedEventEmitter, +} from "matrix-js-sdk/src/matrix"; +import { + CallMembership, + Focus, + MatrixRTCSessionEvent, + MatrixRTCSessionEventHandlerMap, + SessionMembershipData, +} from "matrix-js-sdk/src/matrixrtc"; import { LocalParticipant, LocalTrackPublication, @@ -100,11 +113,40 @@ function mockEmitter(): EmitterMock { }; } +export function mockRtcMembership( + user: string | RoomMember, + deviceId: string, + callId = "", + fociPreferred: Focus[] = [], + focusActive: Focus = { type: "oldest_membership" }, + membership: Partial = {}, +): CallMembership { + const data: SessionMembershipData = { + application: "m.call", + call_id: callId, + device_id: deviceId, + foci_preferred: fociPreferred, + focus_active: focusActive, + ...membership, + }; + const event = new MatrixEvent({ + sender: typeof user === "string" ? user : user.userId, + }); + return new CallMembership(event, data); +} + // Maybe it'd be good to move this to matrix-js-sdk? Our testing needs are // rather simple, but if one util to mock a member is good enough for us, maybe // it's useful for matrix-js-sdk consumers in general. -export function mockMatrixRoomMember(member: Partial): RoomMember { - return { ...mockEmitter(), ...member } as RoomMember; +export function mockMatrixRoomMember( + rtcMembership: CallMembership, + member: Partial = {}, +): RoomMember { + return { + ...mockEmitter(), + userId: rtcMembership.sender, + ...member, + } as RoomMember; } export function mockMatrixRoom(room: Partial): MatrixRoom { @@ -174,14 +216,15 @@ export function mockLocalParticipant( } export async function withLocalMedia( - member: Partial, + localRtcMember: CallMembership, + roomMember: Partial, continuation: (vm: LocalUserMediaViewModel) => void | Promise, ): Promise { const localParticipant = mockLocalParticipant({}); const vm = new LocalUserMediaViewModel( "local", - mockMatrixRoomMember(member), - localParticipant, + mockMatrixRoomMember(localRtcMember, roomMember), + of(localParticipant), { kind: E2eeType.PER_PARTICIPANT, }, @@ -208,15 +251,16 @@ export function mockRemoteParticipant( } export async function withRemoteMedia( - member: Partial, + localRtcMember: CallMembership, + roomMember: Partial, participant: Partial, continuation: (vm: RemoteUserMediaViewModel) => void | Promise, ): Promise { const remoteParticipant = mockRemoteParticipant(participant); const vm = new RemoteUserMediaViewModel( "remote", - mockMatrixRoomMember(member), - remoteParticipant, + mockMatrixRoomMember(localRtcMember, roomMember), + of(remoteParticipant), { kind: E2eeType.PER_PARTICIPANT, }, @@ -244,3 +288,30 @@ export function mockMediaPlay(): string[] { }; return audioIsPlaying; } + +export class MockRTCSession extends TypedEventEmitter< + MatrixRTCSessionEvent, + MatrixRTCSessionEventHandlerMap +> { + public constructor( + public readonly room: Room, + private localMembership: CallMembership, + public memberships: CallMembership[] = [], + ) { + super(); + } + + public withMemberships( + rtcMembers: Observable[]>, + ): MockRTCSession { + rtcMembers.subscribe((m) => { + const old = this.memberships; + // always prepend the local participant + const updated = [this.localMembership, ...(m as CallMembership[])]; + this.memberships = updated; + this.emit(MatrixRTCSessionEvent.MembershipsChanged, old, updated); + }); + + return this; + } +} From 65cc583c8a8868579b9fbc6a1a6103a0cbb2e4c8 Mon Sep 17 00:00:00 2001 From: fkwp <5071496+fkwp@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:04:29 +0000 Subject: [PATCH 28/72] Translations updates --- locales/de/app.json | 6 +- locales/ro/app.json | 195 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 locales/ro/app.json diff --git a/locales/de/app.json b/locales/de/app.json index aa57f824..828fe3e0 100644 --- a/locales/de/app.json +++ b/locales/de/app.json @@ -69,9 +69,10 @@ "video": "Video" }, "developer_mode": { - "crypto_version": "Krypto-Version:{{version}}", + "crypto_version": "Krypto-Version: {{version}}", "device_id": "Geräte-ID: {{id}}", "duplicate_tiles_label": "Anzahl zusätzlicher Kachelkopien pro Teilnehmer", + "hostname": "Hostname: {{hostname}}", "matrix_id": "Matrix-ID: {{id}}" }, "disconnected_banner": "Die Verbindung zum Server wurde getrennt.", @@ -193,6 +194,7 @@ "expand": "Erweitern", "mute_for_me": "Für mich stumm schalten", "muted_for_me": "Für mich stumm geschaltet", - "volume": "Lautstärke" + "volume": "Lautstärke", + "waiting_for_media": "Warten auf Medien..." } } diff --git a/locales/ro/app.json b/locales/ro/app.json new file mode 100644 index 00000000..ab55c511 --- /dev/null +++ b/locales/ro/app.json @@ -0,0 +1,195 @@ +{ + "a11y": { + "user_menu": "Meniul utilizatorului" + }, + "action": { + "close": "Închide", + "copy_link": "Copiază linkul", + "edit": "Editare", + "go": "Du-te", + "invite": "Invită", + "lower_hand": "Mâna inferioară", + "no": "No", + "pick_reaction": "Alegeți reacția", + "raise_hand": "Ridicați mâna", + "register": "Inregistrare", + "remove": "elimina", + "show_less": "Arată mai puțin", + "show_more": "Arată mai mult", + "sign_in": "Autentificare", + "sign_out": "Sign out", + "submit": "Trimiteți", + "upload_file": "Încărcați fișierul" + }, + "analytics_notice": "Prin participarea la această versiune beta, sunteți de acord cu colectarea de date anonime, pe care le folosim pentru a îmbunătăți produsul. Puteți găsi mai multe informații despre datele pe care le urmărim în Politica noastră de <2> confidențialitate și Politica noastră <6> privind cookie-urile.", + "app_selection_modal": { + "continue_in_browser": "Continuați în browser", + "open_in_app": "Deschideți în aplicație", + "text": "Sunteți gata să vă alăturați?", + "title": "Selectați aplicația" + }, + "application_opened_another_tab": "Această aplicație a fost deschisă într-o altă filă.", + "browser_media_e2ee_unsupported": "Browserul dvs. web nu acceptă criptarea media end-to-end. Browserele acceptate sunt Chrome, Safari, Firefox > = 117", + "browser_media_e2ee_unsupported_heading": "Browser incompatibil", + "call_ended_view": { + "body": "Ai fost deconectat de la apel", + "create_account_button": "Creează cont", + "create_account_prompt": "<0>De ce să nu terminați prin configurarea unei parole pentru a vă păstra contul? <1>Veți putea să vă păstrați numele și să setați un avatar pentru a fi utilizat la apelurile viitoare ", + "feedback_done": "<0>Vă mulțumim pentru feedback! ", + "feedback_prompt": "<0>Ne-ar plăcea să auzim feedback-ul dvs., astfel încât să vă putem îmbunătăți experiența. ", + "headline": "{{displayName}}, apelul tău s-a încheiat.", + "not_now_button": "Nu acum, reveniți la ecranul de pornire", + "reconnect_button": "Reconecta", + "survey_prompt": "Cum a mers?" + }, + "call_name": "Numele apelului", + "common": { + "analytics": "Analiză", + "audio": "Audio", + "avatar": "avatar", + "back": "Înapoi", + "camera": "Aparat foto", + "display_name": "Nume afișat", + "encrypted": "Criptat", + "error": "Eroare", + "home": "Acasa", + "loading": "Se încarcă...", + "microphone": "Microfon", + "next": "Urmator\n", + "options": "Opțiuni", + "password": "Parolă", + "preferences": "preferinte", + "profile": "Profil", + "reaction": "Reacție", + "reactions": "Reacții", + "settings": "Settings", + "something_went_wrong": "Ceva nu a mers bine", + "unencrypted": "Nu este criptat", + "username": "Nume utilizator", + "video": "Videoclip" + }, + "developer_mode": { + "crypto_version": "Versiunea Crypto: {{version}}", + "device_id": "ID-ul dispozitivului: {{id}}", + "duplicate_tiles_label": "Numărul de exemplare suplimentare de cartonașe per participant", + "hostname": "Numele gazdei: {{hostname}}", + "matrix_id": "ID-ul matricei: {{id}}" + }, + "disconnected_banner": "Conectivitatea la server a fost pierdută.", + "full_screen_view_description": "<0>Trimiterea jurnalelor de depanare ne va ajuta să urmărim problema. ", + "full_screen_view_h1": "<0>Hopa, ceva nu a mers bine. ", + "group_call_loader": { + "banned_body": "Ai fost interzis să ieși din cameră.", + "banned_heading": "Interzis", + "call_ended_body": "Ați fost eliminat din apel.", + "call_ended_heading": "Apel încheiat", + "failed_heading": "Nu s-a putut alătura", + "failed_text": "Apelul nu a fost găsit sau nu este accesibil.", + "knock_reject_body": "Cererea dvs. de a vă alătura a fost respinsă.", + "knock_reject_heading": "Acces refuzat", + "reason": "Motivul" + }, + "hangup_button_label": "Încheiați apelul", + "header_label": "Element Call Home", + "header_participants_label": "Participanți", + "invite_modal": { + "link_copied_toast": "Link copiat în clipboard", + "title": "Invitați la acest apel" + }, + "join_existing_call_modal": { + "join_button": "Da, alăturați-vă apelului", + "text": "Acest apel există deja, doriți să vă alăturați?", + "title": "Alăturați-vă apelului existent?" + }, + "layout_grid_label": "GRILĂ", + "layout_spotlight_label": "Spotlight", + "lobby": { + "ask_to_join": "Solicitare de participare la apel", + "join_as_guest": "Alăturați-vă ca invitat", + "join_button": "Alăturați-vă apelului", + "leave_button": "Înapoi la cele mai recente", + "waiting_for_invite": "Solicitare trimisă! În așteptarea permisiunii de a participa..." + }, + "log_in": "Autentificare", + "logging_in": "Autentificare...", + "login_auth_links": "<0>Creați un cont sau <2> accesați ca invitat ", + "login_auth_links_prompt": "Nu sunteți încă înregistrat?", + "login_subheading": "Pentru a continua la Element", + "login_title": "Logare", + "microphone_off": "Microfon oprit", + "microphone_on": "Microfon pornit", + "mute_microphone_button_label": "Dezactivați microfonul", + "qr_code": "COD QR", + "rageshake_button_error_caption": "Încearcă din nou trimiterea jurnalelor", + "rageshake_request_modal": { + "body": "Un alt utilizator al acestui apel are o problemă. Pentru a diagnostica mai bine aceste probleme, am dori să colectăm un jurnal de depanare.", + "title": "Solicitare jurnal de depanare" + }, + "rageshake_send_logs": "Trimiteți jurnale de depanare", + "rageshake_sending": "Trimiterea...", + "rageshake_sending_logs": "Trimiterea jurnalelor de depanare...", + "rageshake_sent": "Multumesc!", + "recaptcha_caption": "Acest site este protejat de reCAPTCHA și se aplică Politica de <2> confidențialitate Google și <6> Termenii și condițiile. <9>Făcând clic pe „Înregistrare”, sunteți de acord cu Acordul nostru de licențiere pentru utilizatorul <12> final (EULA) ", + "recaptcha_dismissed": "Recaptcha a fost respins", + "recaptcha_not_loaded": "Recaptcha nu a fost încărcat", + "register": { + "passwords_must_match": "Parolele trebuie să se potrivească", + "registering": "Înregistrare..." + }, + "register_auth_links": "<0>Ai deja un cont? <1><0>Conectați-vă sau <2> accesați ca invitat ", + "register_confirm_password_label": "Confirmă Parola", + "register_heading": "Creează-ți contul", + "return_home_button": "Reveniți la ecranul de pornire", + "room_auth_view_continue_button": "Continuă", + "room_auth_view_eula_caption": "Făcând clic pe „Continuați”, sunteți de acord cu Acordul nostru de licențiere pentru utilizatorul <2> final (EULA) ", + "screenshare_button_label": "Partajare ecran", + "settings": { + "audio_tab": { + "effect_volume_description": "Reglați volumul la care reacționează reacțiile și efectele ridicate de mână", + "effect_volume_label": "Volumul efectului sonor" + }, + "developer_settings_label": "Setări pentru dezvoltatori", + "developer_settings_label_description": "Expuneți setările dezvoltatorului în fereastra de setări.", + "developer_tab_title": "dezvoltator", + "feedback_tab_body": "Dacă întâmpinați probleme sau pur și simplu doriți să oferiți feedback, vă rugăm să ne trimiteți o scurtă descriere mai jos.", + "feedback_tab_description_label": "Feedback-ul tău", + "feedback_tab_h4": "Trimiteți Feedback", + "feedback_tab_send_logs_label": "Includeți jurnale de depanare", + "feedback_tab_thank_you": "Vă mulțumim, am primit feedback-ul dvs.!", + "feedback_tab_title": "Feedback", + "more_tab_title": "Mai mult", + "opt_in_description": "<0><1>Puteți retrage consimțământul debifând această casetă. Dacă sunteți în prezent la un apel, această setare va intra în vigoare la sfârșitul apelului.", + "preferences_tab": { + "reactions_play_sound_description": "Redați un efect sonor atunci când cineva trimite o reacție la un apel.", + "reactions_play_sound_label": "Redați sunete de reacție", + "reactions_show_description": "Afișați o animație atunci când cineva trimite o reacție.", + "reactions_show_label": "Afișați reacțiile", + "reactions_title": "Reacții" + }, + "preferences_tab_body": "Aici puteți configura opțiuni suplimentare pentru o experiență îmbunătățită", + "preferences_tab_h4": "preferinte", + "preferences_tab_show_hand_raised_timer_description": "Afișați un cronometru atunci când un participant ridică mâna", + "preferences_tab_show_hand_raised_timer_label": "Afișați durata ridicării mâinii", + "speaker_device_selection_label": "vorbitor" + }, + "start_new_call": "Începe un nou apel", + "start_video_button_label": "Începeți videoclipul", + "stop_screenshare_button_label": "Partajarea ecranului", + "stop_video_button_label": "Opriți videoclipul", + "submitting": "Trimiterea...", + "switch_camera": "Comutați camera", + "unauthenticated_view_body": "Nu sunteți încă înregistrat? <2>Creați un cont ", + "unauthenticated_view_eula_caption": "Făcând clic pe „Go”, sunteți de acord cu Acordul nostru de licențiere pentru utilizatorul <2> final (EULA) ", + "unauthenticated_view_login_button": "Conectați-vă la contul dvs.", + "unmute_microphone_button_label": "Anulează microfonul", + "version": "{{productName}}Versiune: {{version}}", + "video_tile": { + "always_show": "Arată întotdeauna", + "change_fit_contain": "Se potrivește cadrului", + "collapse": "colaps", + "expand": "Extindeți", + "mute_for_me": "Mute pentru mine", + "muted_for_me": "Dezactivat pentru mine", + "volume": "VOLUM" + } +} From 4fab1a25b4ad1a263dad15c8dec0f98a1e5fbac0 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 6 Dec 2024 18:15:49 +0000 Subject: [PATCH 29/72] Use specific Node.js version (22) instead of latest LTS (#2879) This way we can manage the upgrade cycle ourselves. --- .github/workflows/element-call.yaml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yaml | 2 +- .github/workflows/translations-download.yaml | 2 +- .node-version | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .node-version diff --git a/.github/workflows/element-call.yaml b/.github/workflows/element-call.yaml index 7924140d..a424fb74 100644 --- a/.github/workflows/element-call.yaml +++ b/.github/workflows/element-call.yaml @@ -28,7 +28,7 @@ jobs: uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 with: cache: "yarn" - node-version: "lts/*" + node-version-file: ".node-version" - name: Install dependencies run: "yarn install" - name: Build diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 69493ff6..d9367626 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,7 +12,7 @@ jobs: uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 with: cache: "yarn" - node-version: "lts/*" + node-version-file: ".node-version" - name: Install dependencies run: "yarn install" - name: Prettier diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b63eb283..a1c7f232 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,7 +14,7 @@ jobs: uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 with: cache: "yarn" - node-version: "lts/*" + node-version-file: ".node-version" - name: Install dependencies run: "yarn install" - name: Vitest diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index 7359f781..30ce6ff9 100644 --- a/.github/workflows/translations-download.yaml +++ b/.github/workflows/translations-download.yaml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 with: cache: "yarn" - node-version: "lts/*" + node-version-file: ".node-version" - name: Install Deps run: "yarn install --frozen-lockfile" diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..2bd5a0a9 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22 From 9d4cd211ed6965b58adb5d0a1990040c8cd8d6af Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 6 Dec 2024 18:30:05 +0000 Subject: [PATCH 30/72] Authenticate media requests when loading avatars (#2856) * Load authenicated media * lint * Add tests * Add widget behaviour and test. * Update src/Avatar.test.tsx Co-authored-by: Hugh Nimmo-Smith --------- Co-authored-by: Hugh Nimmo-Smith --- src/Avatar.test.tsx | 156 ++++++++++++++++++++++++++++++++++++++++++ src/Avatar.tsx | 80 +++++++++++++++++++--- src/ClientContext.tsx | 9 +++ src/utils/matrix.ts | 12 ---- 4 files changed, 236 insertions(+), 21 deletions(-) create mode 100644 src/Avatar.test.tsx diff --git a/src/Avatar.test.tsx b/src/Avatar.test.tsx new file mode 100644 index 00000000..7eee2e90 --- /dev/null +++ b/src/Avatar.test.tsx @@ -0,0 +1,156 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { afterEach, expect, test, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { FC, PropsWithChildren } from "react"; + +import { ClientContextProvider } from "./ClientContext"; +import { Avatar } from "./Avatar"; +import { mockMatrixRoomMember, mockRtcMembership } from "./utils/test"; + +const TestComponent: FC< + PropsWithChildren<{ client: MatrixClient; supportsThumbnails?: boolean }> +> = ({ client, children, supportsThumbnails }) => { + return ( + + {children} + + ); +}; + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +test("should just render a placeholder when the user has no avatar", () => { + const client = vi.mocked({ + getAccessToken: () => "my-access-token", + mxcUrlToHttp: () => vi.fn(), + } as unknown as MatrixClient); + + vi.spyOn(client, "mxcUrlToHttp"); + const member = mockMatrixRoomMember( + mockRtcMembership("@alice:example.org", "AAAA"), + { + getMxcAvatarUrl: () => undefined, + }, + ); + const displayName = "Alice"; + render( + + + , + ); + const element = screen.getByRole("img", { name: "@alice:example.org" }); + expect(element.tagName).toEqual("SPAN"); + expect(client.mxcUrlToHttp).toBeCalledTimes(0); +}); + +test("should just render a placeholder when thumbnails are not supported", () => { + const client = vi.mocked({ + getAccessToken: () => "my-access-token", + mxcUrlToHttp: () => vi.fn(), + } as unknown as MatrixClient); + + vi.spyOn(client, "mxcUrlToHttp"); + const member = mockMatrixRoomMember( + mockRtcMembership("@alice:example.org", "AAAA"), + { + getMxcAvatarUrl: () => "mxc://example.org/alice-avatar", + }, + ); + const displayName = "Alice"; + render( + + + , + ); + const element = screen.getByRole("img", { name: "@alice:example.org" }); + expect(element.tagName).toEqual("SPAN"); + expect(client.mxcUrlToHttp).toBeCalledTimes(0); +}); + +test("should attempt to fetch authenticated media", async () => { + const expectedAuthUrl = "http://example.org/media/alice-avatar"; + const expectedObjectURL = "my-object-url"; + const accessToken = "my-access-token"; + const theBlob = new Blob([]); + + // vitest doesn't have a implementation of create/revokeObjectURL, so we need + // to delete the property. It's a bit odd, but it works. + Reflect.deleteProperty(global.window.URL, "createObjectURL"); + globalThis.URL.createObjectURL = vi.fn().mockReturnValue(expectedObjectURL); + Reflect.deleteProperty(global.window.URL, "revokeObjectURL"); + globalThis.URL.revokeObjectURL = vi.fn(); + + const fetchFn = vi.fn().mockResolvedValue({ + blob: async () => Promise.resolve(theBlob), + }); + vi.stubGlobal("fetch", fetchFn); + + const client = vi.mocked({ + getAccessToken: () => accessToken, + mxcUrlToHttp: () => vi.fn(), + } as unknown as MatrixClient); + + vi.spyOn(client, "mxcUrlToHttp").mockReturnValue(expectedAuthUrl); + const member = mockMatrixRoomMember( + mockRtcMembership("@alice:example.org", "AAAA"), + { + getMxcAvatarUrl: () => "mxc://example.org/alice-avatar", + }, + ); + const displayName = "Alice"; + render( + + + , + ); + + // Fetch is asynchronous, so wait for this to resolve. + await vi.waitUntil(() => + document.querySelector(`img[src='${expectedObjectURL}']`), + ); + + expect(client.mxcUrlToHttp).toBeCalledTimes(1); + expect(globalThis.fetch).toBeCalledWith(expectedAuthUrl, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); +}); diff --git a/src/Avatar.tsx b/src/Avatar.tsx index 29ab5236..f3fe6cd8 100644 --- a/src/Avatar.tsx +++ b/src/Avatar.tsx @@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { useMemo, FC, CSSProperties } from "react"; +import { useMemo, FC, CSSProperties, useState, useEffect } from "react"; import { Avatar as CompoundAvatar } from "@vector-im/compound-web"; +import { MatrixClient } from "matrix-js-sdk/src/client"; -import { getAvatarUrl } from "./utils/matrix"; -import { useClient } from "./ClientContext"; +import { useClientState } from "./ClientContext"; export enum Size { XS = "xs", @@ -36,6 +36,28 @@ interface Props { style?: CSSProperties; } +export function getAvatarUrl( + client: MatrixClient, + mxcUrl: string | null, + avatarSize = 96, +): string | null { + const width = Math.floor(avatarSize * window.devicePixelRatio); + const height = Math.floor(avatarSize * window.devicePixelRatio); + // scale is more suitable for larger sizes + const resizeMethod = avatarSize <= 96 ? "crop" : "scale"; + return mxcUrl + ? client.mxcUrlToHttp( + mxcUrl, + width, + height, + resizeMethod, + false, + true, + true, + ) + : null; +} + export const Avatar: FC = ({ className, id, @@ -45,7 +67,7 @@ export const Avatar: FC = ({ style, ...props }) => { - const { client } = useClient(); + const clientState = useClientState(); const sizePx = useMemo( () => @@ -55,10 +77,50 @@ export const Avatar: FC = ({ [size], ); - const resolvedSrc = useMemo(() => { - if (!client || !src || !sizePx) return undefined; - return src.startsWith("mxc://") ? getAvatarUrl(client, src, sizePx) : src; - }, [client, src, sizePx]); + const [avatarUrl, setAvatarUrl] = useState(undefined); + + useEffect(() => { + if (clientState?.state !== "valid") { + return; + } + const { authenticated, supportedFeatures } = clientState; + const client = authenticated?.client; + + if (!client || !src || !sizePx || !supportedFeatures.thumbnails) { + return; + } + + const token = client.getAccessToken(); + if (!token) { + return; + } + const resolveSrc = getAvatarUrl(client, src, sizePx); + if (!resolveSrc) { + setAvatarUrl(undefined); + return; + } + + let objectUrl: string | undefined; + fetch(resolveSrc, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then(async (req) => req.blob()) + .then((blob) => { + objectUrl = URL.createObjectURL(blob); + setAvatarUrl(objectUrl); + }) + .catch((ex) => { + setAvatarUrl(undefined); + }); + + return (): void => { + if (objectUrl) { + URL.revokeObjectURL(objectUrl); + } + }; + }, [clientState, src, sizePx]); return ( = ({ id={id} name={name} size={`${sizePx}px`} - src={resolvedSrc} + src={avatarUrl} style={style} {...props} /> diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 8b5589d5..7a37e750 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -48,6 +48,7 @@ export type ValidClientState = { disconnected: boolean; supportedFeatures: { reactions: boolean; + thumbnails: boolean; }; setClient: (params?: SetClientParams) => void; }; @@ -71,6 +72,8 @@ export type SetClientParams = { const ClientContext = createContext(undefined); +export const ClientContextProvider = ClientContext.Provider; + export const useClientState = (): ClientState | undefined => useContext(ClientContext); @@ -253,6 +256,7 @@ export const ClientProvider: FC = ({ children }) => { const [isDisconnected, setIsDisconnected] = useState(false); const [supportsReactions, setSupportsReactions] = useState(false); + const [supportsThumbnails, setSupportsThumbnails] = useState(false); const state: ClientState | undefined = useMemo(() => { if (alreadyOpenedErr) { @@ -278,6 +282,7 @@ export const ClientProvider: FC = ({ children }) => { disconnected: isDisconnected, supportedFeatures: { reactions: supportsReactions, + thumbnails: supportsThumbnails, }, }; }, [ @@ -288,6 +293,7 @@ export const ClientProvider: FC = ({ children }) => { setClient, isDisconnected, supportsReactions, + supportsThumbnails, ]); const onSync = useCallback( @@ -313,6 +319,8 @@ export const ClientProvider: FC = ({ children }) => { } if (initClientState.widgetApi) { + // There is currently no widget API for authenticated media thumbnails. + setSupportsThumbnails(false); const reactSend = initClientState.widgetApi.hasCapability( "org.matrix.msc2762.send.event:m.reaction", ); @@ -334,6 +342,7 @@ export const ClientProvider: FC = ({ children }) => { } } else { setSupportsReactions(true); + setSupportsThumbnails(true); } return (): void => { diff --git a/src/utils/matrix.ts b/src/utils/matrix.ts index d3821a3f..63b6ef67 100644 --- a/src/utils/matrix.ts +++ b/src/utils/matrix.ts @@ -333,15 +333,3 @@ export function getRelativeRoomUrl( : ""; return `/room/#${roomPart}?${generateUrlSearchParams(roomId, encryptionSystem, viaServers).toString()}`; } - -export function getAvatarUrl( - client: MatrixClient, - mxcUrl: string, - avatarSize = 96, -): string { - const width = Math.floor(avatarSize * window.devicePixelRatio); - const height = Math.floor(avatarSize * window.devicePixelRatio); - // scale is more suitable for larger sizes - const resizeMethod = avatarSize <= 96 ? "crop" : "scale"; - return mxcUrl && client.mxcUrlToHttp(mxcUrl, width, height, resizeMethod)!; -} From a8a95c3f0024f4f3b90b753ad5e6571aaa31ef4e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 9 Dec 2024 11:39:16 +0000 Subject: [PATCH 31/72] Ensure call sound effects are played over the correct sink (#2863) * Refactor to use AudioContext * Remove unused audio format. * Reduce update frequency for volume * Port to useAudioContext * Port reactionaudiorenderer to useAudioContext * Integrate raise hand sound into call event renderer. * Simplify reaction sounds * only play one sound per reaction type * Start to build out tests * fixup tests / comments * Fix reaction sound * remove console line * Remove another debug line. * fix lint * Use testing library click * lint * fix a few things * Change the way we as unknown the mock RTC session. * Lint * Fix types for MockRTCSession * value change should always be set * Update volume slider description. * Only load reaction sound effects if enabled. * cache improvements * lowercase soundMap * lint * move prefetch sounds to fix hot reload * correct docs * add a header * Wording change --------- Co-authored-by: Hugh Nimmo-Smith --- locales/de/app.json | 1 - locales/en/app.json | 2 +- src/Slider.tsx | 3 + src/livekit/MediaDevicesContext.tsx | 4 +- src/room/CallEventAudioRenderer.test.tsx | 294 ++++++++--------------- src/room/CallEventAudioRenderer.tsx | 118 ++++----- src/room/GroupCallView.tsx | 2 +- src/room/InCallView.tsx | 36 +-- src/room/ReactionAudioRenderer.test.tsx | 95 ++++---- src/room/ReactionAudioRenderer.tsx | 91 ++++--- src/settings/SettingsModal.tsx | 9 +- src/soundUtils.ts | 63 +++++ src/state/CallViewModel.ts | 2 +- src/useAudioContext.test.tsx | 129 ++++++++++ src/useAudioContext.tsx | 124 ++++++++++ src/utils/test.ts | 38 --- src/utils/testReactions.tsx | 10 +- 17 files changed, 575 insertions(+), 446 deletions(-) create mode 100644 src/soundUtils.ts create mode 100644 src/useAudioContext.test.tsx create mode 100644 src/useAudioContext.tsx diff --git a/locales/de/app.json b/locales/de/app.json index 828fe3e0..67138ca5 100644 --- a/locales/de/app.json +++ b/locales/de/app.json @@ -147,7 +147,6 @@ "screenshare_button_label": "Bildschirm teilen", "settings": { "audio_tab": { - "effect_volume_description": "Lautstärke anpassen, mit der Reaktionen und Handmeldungen abgespielt werden", "effect_volume_label": "Lautstärke der Soundeffekte" }, "developer_settings_label": "Entwicklereinstellungen", diff --git a/locales/en/app.json b/locales/en/app.json index e500f66c..07dd53e3 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -147,7 +147,7 @@ "screenshare_button_label": "Share screen", "settings": { "audio_tab": { - "effect_volume_description": "Adjust the volume at which reactions and hand raised effects play", + "effect_volume_description": "Volume for sound effects such as: joining or leaving a call, and reactions.", "effect_volume_label": "Sound effect volume" }, "developer_settings_label": "Developer Settings", diff --git a/src/Slider.tsx b/src/Slider.tsx index a5eab56a..e398fba1 100644 --- a/src/Slider.tsx +++ b/src/Slider.tsx @@ -16,6 +16,9 @@ interface Props { className?: string; label: string; value: number; + /** + * Event handler called when the value changes during an interaction. + */ onValueChange: (value: number) => void; /** * Event handler called when the value changes at the end of an interaction. diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 4977f021..d19840ef 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -115,12 +115,12 @@ function useMediaDevice( }, [available, preferredId, select, alwaysDefault]); } -const deviceStub: MediaDevice = { +export const deviceStub: MediaDevice = { available: [], selectedId: undefined, select: () => {}, }; -const devicesStub: MediaDevices = { +export const devicesStub: MediaDevices = { audioInput: deviceStub, audioOutput: deviceStub, videoInput: deviceStub, diff --git a/src/room/CallEventAudioRenderer.test.tsx b/src/room/CallEventAudioRenderer.test.tsx index 78d08f60..5bb1ba19 100644 --- a/src/room/CallEventAudioRenderer.test.tsx +++ b/src/room/CallEventAudioRenderer.test.tsx @@ -6,25 +6,30 @@ Please see LICENSE in the repository root for full details. */ import { render } from "@testing-library/react"; -import { beforeEach, expect, test } from "vitest"; +import { + afterAll, + beforeEach, + expect, + MockedFunction, + test, + vitest, +} from "vitest"; import { MatrixClient } from "matrix-js-sdk/src/client"; -import { ConnectionState, Room } from "livekit-client"; +import { ConnectionState } from "livekit-client"; import { BehaviorSubject, of } from "rxjs"; import { afterEach } from "node:test"; -import { act } from "react"; +import { act, ReactNode } from "react"; import { CallMembership, type MatrixRTCSession, } from "matrix-js-sdk/src/matrixrtc"; +import { RoomMember } from "matrix-js-sdk/src/matrix"; -import { soundEffectVolumeSetting } from "../settings/settings"; import { - EmittableMockLivekitRoom, mockLivekitRoom, mockLocalParticipant, mockMatrixRoom, mockMatrixRoomMember, - mockMediaPlay, mockRemoteParticipant, mockRtcMembership, MockRTCSession, @@ -35,37 +40,69 @@ import { CallEventAudioRenderer, MAX_PARTICIPANT_COUNT_FOR_SOUND, } from "./CallEventAudioRenderer"; +import { useAudioContext } from "../useAudioContext"; +import { TestReactionsWrapper } from "../utils/testReactions"; +import { prefetchSounds } from "../soundUtils"; const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); const local = mockMatrixRoomMember(localRtcMember); const aliceRtcMember = mockRtcMembership("@alice:example.org", "AAAA"); const alice = mockMatrixRoomMember(aliceRtcMember); const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB"); -const bob = mockMatrixRoomMember(bobRtcMember); const localParticipant = mockLocalParticipant({ identity: "" }); const aliceId = `${alice.userId}:${aliceRtcMember.deviceId}`; -const bobId = `${bob.userId}:${bobRtcMember.deviceId}`; const aliceParticipant = mockRemoteParticipant({ identity: aliceId }); -const bobParticipant = mockRemoteParticipant({ identity: bobId }); -const originalPlayFn = window.HTMLMediaElement.prototype.play; - -const enterSound = "http://localhost:3000/src/sound/join_call.ogg"; -const leaveSound = "http://localhost:3000/src/sound/left_call.ogg"; - -beforeEach(() => { - soundEffectVolumeSetting.setValue(soundEffectVolumeSetting.defaultValue); -}); +vitest.mock("../useAudioContext"); +vitest.mock("../soundUtils"); afterEach(() => { - window.HTMLMediaElement.prototype.play = originalPlayFn; + vitest.resetAllMocks(); }); -test("plays a sound when entering a call", () => { - const audioIsPlaying: string[] = mockMediaPlay(); - const matrixRoomMembers = new Map( - [local, alice, bob].map((p) => [p.userId, p]), +afterAll(() => { + vitest.restoreAllMocks(); +}); + +let playSound: MockedFunction< + NonNullable>["playSound"] +>; + +beforeEach(() => { + (prefetchSounds as MockedFunction).mockResolvedValue({ + sound: new ArrayBuffer(0), + }); + playSound = vitest.fn(); + (useAudioContext as MockedFunction).mockReturnValue({ + playSound, + }); +}); + +function TestComponent({ + rtcSession, + vm, +}: { + rtcSession: MockRTCSession; + vm: CallViewModel; +}): ReactNode { + return ( + + + ); +} + +function getMockEnv( + members: RoomMember[], + initialRemoteRtcMemberships: CallMembership[] = [aliceRtcMember], +): { + vm: CallViewModel; + session: MockRTCSession; + remoteRtcMemberships: BehaviorSubject; +} { + const matrixRoomMembers = new Map(members.map((p) => [p.userId, p])); const remoteParticipants = of([aliceParticipant]); const liveKitRoom = mockLivekitRoom( { localParticipant }, @@ -75,221 +112,90 @@ test("plays a sound when entering a call", () => { client: { getUserId: () => localRtcMember.sender, getDeviceId: () => localRtcMember.deviceId, + on: vitest.fn(), + off: vitest.fn(), } as Partial as MatrixClient, getMember: (userId) => matrixRoomMembers.get(userId) ?? null, }); - const session = new MockRTCSession(matrixRoom, localRtcMember, [ - aliceRtcMember, - ]) as unknown as MatrixRTCSession; - - const vm = new CallViewModel( - session, - liveKitRoom, - { - kind: E2eeType.PER_PARTICIPANT, - }, - of(ConnectionState.Connected), + const remoteRtcMemberships = new BehaviorSubject( + initialRemoteRtcMemberships, ); - render(); - expect(audioIsPlaying).toEqual([ - // Joining the call - enterSound, - ]); -}); - -test("plays no sound when muted", () => { - soundEffectVolumeSetting.setValue(0); - const audioIsPlaying: string[] = mockMediaPlay(); - const matrixRoomMembers = new Map( - [local, alice, bob].map((p) => [p.userId, p]), - ); - const remoteParticipants = of([aliceParticipant, bobParticipant]); - const liveKitRoom = mockLivekitRoom( - { localParticipant }, - { remoteParticipants }, - ); - - const matrixRoom = mockMatrixRoom({ - client: { - getUserId: () => localRtcMember.sender, - getDeviceId: () => localRtcMember.deviceId, - } as Partial as MatrixClient, - getMember: (userId) => matrixRoomMembers.get(userId) ?? null, - }); - - const session = new MockRTCSession(matrixRoom, localRtcMember, [ - aliceRtcMember, - ]) as unknown as MatrixRTCSession; - - const vm = new CallViewModel( - session, - liveKitRoom, - { - kind: E2eeType.PER_PARTICIPANT, - }, - of(ConnectionState.Connected), - ); - - render(); - // Play a sound when joining a call. - expect(audioIsPlaying).toHaveLength(0); -}); - -test("plays a sound when a user joins", () => { - const audioIsPlaying: string[] = mockMediaPlay(); - const matrixRoomMembers = new Map([local, alice].map((p) => [p.userId, p])); - const remoteParticipants = new Map( - [aliceParticipant].map((p) => [p.identity, p]), - ); - const liveKitRoom = new EmittableMockLivekitRoom({ - localParticipant, - remoteParticipants, - }); - - const matrixRoom = mockMatrixRoom({ - client: { - getUserId: () => localRtcMember.sender, - getDeviceId: () => localRtcMember.deviceId, - } as Partial as MatrixClient, - getMember: (userId) => matrixRoomMembers.get(userId) ?? null, - }); - - const remoteRtcMemberships = new BehaviorSubject([ - aliceRtcMember, - ]); - // we give Bob an RTC session now, but no participant yet const session = new MockRTCSession( matrixRoom, localRtcMember, - ).withMemberships( - remoteRtcMemberships.asObservable(), - ) as unknown as MatrixRTCSession; + ).withMemberships(remoteRtcMemberships); const vm = new CallViewModel( - session, - liveKitRoom as unknown as Room, + session as unknown as MatrixRTCSession, + liveKitRoom, { kind: E2eeType.PER_PARTICIPANT, }, of(ConnectionState.Connected), ); - render(); + return { vm, session, remoteRtcMemberships }; +} + +/** + * We don't want to play a sound when loading the call state + * because typically this occurs in two stages. We first join + * the call as a local participant and *then* the remote + * participants join from our perspective. We don't want to make + * a noise every time. + */ +test("plays one sound when entering a call", () => { + const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]); + render(); + // Joining a call usually means remote participants are added later. + act(() => { + remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]); + }); + expect(playSound).toHaveBeenCalledOnce(); +}); + +// TODO: Same test? +test("plays a sound when a user joins", () => { + const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]); + render(); act(() => { remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]); }); // Play a sound when joining a call. - expect(audioIsPlaying).toEqual([ - // Joining the call - enterSound, - // Bob joins - enterSound, - ]); + expect(playSound).toBeCalledWith("join"); }); test("plays a sound when a user leaves", () => { - const audioIsPlaying: string[] = mockMediaPlay(); - const matrixRoomMembers = new Map([local, alice].map((p) => [p.userId, p])); - const remoteParticipants = new Map( - [aliceParticipant].map((p) => [p.identity, p]), - ); - const liveKitRoom = new EmittableMockLivekitRoom({ - localParticipant, - remoteParticipants, - }); - - const matrixRoom = mockMatrixRoom({ - client: { - getUserId: () => localRtcMember.sender, - getDeviceId: () => localRtcMember.deviceId, - } as Partial as MatrixClient, - getMember: (userId) => matrixRoomMembers.get(userId) ?? null, - }); - - const remoteRtcMemberships = new BehaviorSubject([ - aliceRtcMember, - ]); - - const session = new MockRTCSession( - matrixRoom, - localRtcMember, - ).withMemberships(remoteRtcMemberships) as unknown as MatrixRTCSession; - - const vm = new CallViewModel( - session, - liveKitRoom as unknown as Room, - { - kind: E2eeType.PER_PARTICIPANT, - }, - of(ConnectionState.Connected), - ); - render(); + const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]); + render(); act(() => { remoteRtcMemberships.next([]); }); - expect(audioIsPlaying).toEqual([ - // Joining the call - enterSound, - // Alice leaves - leaveSound, - ]); + expect(playSound).toBeCalledWith("left"); }); -test("plays no sound when the session member count is larger than the max, until decreased", () => { - const audioIsPlaying: string[] = mockMediaPlay(); - const matrixRoomMembers = new Map([local, alice].map((p) => [p.userId, p])); - const remoteParticipants = new Map( - [aliceParticipant].map((p) => [p.identity, p]), - ); - +test("plays no sound when the participant list is more than the maximum size", () => { const mockRtcMemberships: CallMembership[] = []; - for (let i = 0; i < MAX_PARTICIPANT_COUNT_FOR_SOUND; i++) { mockRtcMemberships.push( mockRtcMembership(`@user${i}:example.org`, `DEVICE${i}`), ); } - const remoteRtcMemberships = new BehaviorSubject( + const { session, vm, remoteRtcMemberships } = getMockEnv( + [local, alice], mockRtcMemberships, ); - const liveKitRoom = new EmittableMockLivekitRoom({ - localParticipant, - remoteParticipants, - }); - - const matrixRoom = mockMatrixRoom({ - client: { - getUserId: () => localRtcMember.sender, - getDeviceId: () => localRtcMember.deviceId, - } as Partial as MatrixClient, - getMember: (userId) => matrixRoomMembers.get(userId) ?? null, - }); - - const session = new MockRTCSession( - matrixRoom, - localRtcMember, - ).withMemberships(remoteRtcMemberships) as unknown as MatrixRTCSession; - - const vm = new CallViewModel( - session, - liveKitRoom as unknown as Room, - { - kind: E2eeType.PER_PARTICIPANT, - }, - of(ConnectionState.Connected), - ); - render(); - expect(audioIsPlaying).toEqual([]); - // When the count drops to the max we should play the leave sound + render(); + expect(playSound).not.toBeCalled(); act(() => { remoteRtcMemberships.next( mockRtcMemberships.slice(0, MAX_PARTICIPANT_COUNT_FOR_SOUND - 1), ); }); - expect(audioIsPlaying).toEqual([leaveSound]); + expect(playSound).toBeCalledWith("left"); }); diff --git a/src/room/CallEventAudioRenderer.tsx b/src/room/CallEventAudioRenderer.tsx index 4f8213cf..6f4f0359 100644 --- a/src/room/CallEventAudioRenderer.tsx +++ b/src/room/CallEventAudioRenderer.tsx @@ -5,47 +5,76 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ReactNode, useEffect, useRef } from "react"; -import { filter } from "rxjs"; +import { ReactNode, useDeferredValue, useEffect, useMemo } from "react"; +import { filter, interval, throttle } from "rxjs"; -import { - soundEffectVolumeSetting as effectSoundVolumeSetting, - useSetting, -} from "../settings/settings"; import { CallViewModel } from "../state/CallViewModel"; -import enterCallSoundMp3 from "../sound/join_call.mp3"; -import enterCallSoundOgg from "../sound/join_call.ogg"; +import joinCallSoundMp3 from "../sound/join_call.mp3"; +import joinCallSoundOgg from "../sound/join_call.ogg"; import leftCallSoundMp3 from "../sound/left_call.mp3"; import leftCallSoundOgg from "../sound/left_call.ogg"; +import handSoundOgg from "../sound/raise_hand.ogg?url"; +import handSoundMp3 from "../sound/raise_hand.mp3?url"; +import { useAudioContext } from "../useAudioContext"; +import { prefetchSounds } from "../soundUtils"; +import { useReactions } from "../useReactions"; +import { useLatest } from "../useLatest"; // Do not play any sounds if the participant count has exceeded this // number. export const MAX_PARTICIPANT_COUNT_FOR_SOUND = 8; -export const CONCURRENT_AUDIO_CHANNELS = 2; +export const THROTTLE_SOUND_EFFECT_MS = 500; + +const sounds = prefetchSounds({ + join: { + mp3: joinCallSoundMp3, + ogg: joinCallSoundOgg, + }, + left: { + mp3: leftCallSoundMp3, + ogg: leftCallSoundOgg, + }, + raiseHand: { + mp3: handSoundMp3, + ogg: handSoundOgg, + }, +}); export function CallEventAudioRenderer({ vm, }: { vm: CallViewModel; }): ReactNode { - const [effectSoundVolume] = useSetting(effectSoundVolumeSetting); - const callEntered = useRef<(HTMLAudioElement | null)[]>([]); - const callLeft = useRef<(HTMLAudioElement | null)[]>([]); + const audioEngineCtx = useAudioContext({ + sounds, + latencyHint: "interactive", + }); + const audioEngineRef = useLatest(audioEngineCtx); + + const { raisedHands } = useReactions(); + const raisedHandCount = useMemo( + () => Object.keys(raisedHands).length, + [raisedHands], + ); + const previousRaisedHandCount = useDeferredValue(raisedHandCount); useEffect(() => { - if (effectSoundVolume === 0) { - return; + if (audioEngineRef.current && previousRaisedHandCount < raisedHandCount) { + audioEngineRef.current.playSound("raiseHand"); } + }, [audioEngineRef, previousRaisedHandCount, raisedHandCount]); + + useEffect(() => { const joinSub = vm.memberChanges .pipe( filter( ({ joined, ids }) => ids.length <= MAX_PARTICIPANT_COUNT_FOR_SOUND && joined.length > 0, ), + throttle(() => interval(THROTTLE_SOUND_EFFECT_MS)), ) - .subscribe(({ joined }) => { - const availablePlayer = callEntered.current.find((v) => v?.paused); - void availablePlayer?.play(); + .subscribe(() => { + audioEngineRef.current?.playSound("join"); }); const leftSub = vm.memberChanges @@ -54,64 +83,17 @@ export function CallEventAudioRenderer({ ({ ids, left }) => ids.length <= MAX_PARTICIPANT_COUNT_FOR_SOUND && left.length > 0, ), + throttle(() => interval(THROTTLE_SOUND_EFFECT_MS)), ) .subscribe(() => { - const availablePlayer = callLeft.current.find((v) => v?.paused); - void availablePlayer?.play(); + audioEngineRef.current?.playSound("left"); }); return (): void => { joinSub.unsubscribe(); leftSub.unsubscribe(); }; - }, [effectSoundVolume, callEntered, callLeft, vm]); + }, [audioEngineRef, vm]); - // Set volume. - useEffect(() => { - callEntered.current.forEach((a) => { - if (a) { - a.volume = effectSoundVolume; - } - }); - callLeft.current.forEach((a) => { - if (a) { - a.volume = effectSoundVolume; - } - }); - }, [callEntered, callLeft, effectSoundVolume]); - - // Do not render any audio elements if playback is disabled. Will save - // audio file fetches. - if (effectSoundVolume === 0) { - return null; - } - - return ( - // Will play as soon as it's mounted, which is what we want as this will - // play when the call is entered. - <> - {Array.from({ length: CONCURRENT_AUDIO_CHANNELS }).map((_, index) => ( - - ))} - {Array.from({ length: CONCURRENT_AUDIO_CHANNELS }).map((_, index) => ( - - ))} - - ); + return <>; } diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index b0fa8c69..9336ffdd 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -331,7 +331,7 @@ export const GroupCallView: FC = ({ = ({ connState, onShareClick, }) => { - const [soundEffectVolume] = useSetting(soundEffectVolumeSetting); - const { supportsReactions, raisedHands, sendReaction, toggleRaisedHand } = - useReactions(); - const raisedHandCount = useMemo( - () => Object.keys(raisedHands).length, - [raisedHands], - ); - const previousRaisedHandCount = useDeferredValue(raisedHandCount); + const { supportsReactions, sendReaction, toggleRaisedHand } = useReactions(); useWakeLock(); @@ -335,25 +324,6 @@ export const InCallView: FC = ({ [vm], ); - // Play a sound when the raised hand count increases. - const handRaisePlayer = useRef(null); - useEffect(() => { - if (!handRaisePlayer.current) { - return; - } - if (previousRaisedHandCount < raisedHandCount) { - handRaisePlayer.current.volume = soundEffectVolume; - handRaisePlayer.current.play().catch((ex) => { - logger.warn("Failed to play raise hand sound", ex); - }); - } - }, [ - raisedHandCount, - handRaisePlayer, - previousRaisedHandCount, - soundEffectVolume, - ]); - useEffect(() => { widget?.api.transport .send( @@ -667,10 +637,6 @@ export const InCallView: FC = ({ {renderContent()} - {footer} diff --git a/src/room/ReactionAudioRenderer.test.tsx b/src/room/ReactionAudioRenderer.test.tsx index 80ae2b38..2fec8a9a 100644 --- a/src/room/ReactionAudioRenderer.test.tsx +++ b/src/room/ReactionAudioRenderer.test.tsx @@ -6,9 +6,18 @@ Please see LICENSE in the repository root for full details. */ import { render } from "@testing-library/react"; -import { afterAll, expect, test } from "vitest"; +import { + afterAll, + beforeEach, + expect, + test, + vitest, + MockedFunction, + Mock, +} from "vitest"; import { TooltipProvider } from "@vector-im/compound-web"; import { act, ReactNode } from "react"; +import { afterEach } from "node:test"; import { MockRoom, @@ -16,12 +25,13 @@ import { TestReactionsWrapper, } from "../utils/testReactions"; import { ReactionsAudioRenderer } from "./ReactionAudioRenderer"; -import { GenericReaction, ReactionSet } from "../reactions"; import { playReactionsSound, soundEffectVolumeSetting, } from "../settings/settings"; -import { mockMediaPlay } from "../utils/test"; +import { useAudioContext } from "../useAudioContext"; +import { GenericReaction, ReactionSet } from "../reactions"; +import { prefetchSounds } from "../soundUtils"; const memberUserIdAlice = "@alice:example.org"; const memberUserIdBob = "@bob:example.org"; @@ -50,11 +60,31 @@ function TestComponent({ ); } -const originalPlayFn = window.HTMLMediaElement.prototype.play; -afterAll(() => { +vitest.mock("../useAudioContext"); +vitest.mock("../soundUtils"); + +afterEach(() => { + vitest.resetAllMocks(); playReactionsSound.setValue(playReactionsSound.defaultValue); soundEffectVolumeSetting.setValue(soundEffectVolumeSetting.defaultValue); - window.HTMLMediaElement.prototype.play = originalPlayFn; +}); + +afterAll(() => { + vitest.restoreAllMocks(); +}); + +let playSound: Mock< + NonNullable>["playSound"] +>; + +beforeEach(() => { + (prefetchSounds as MockedFunction).mockResolvedValue({ + sound: new ArrayBuffer(0), + }); + playSound = vitest.fn(); + (useAudioContext as MockedFunction).mockReturnValue({ + playSound, + }); }); test("preloads all audio elements", () => { @@ -63,25 +93,11 @@ test("preloads all audio elements", () => { new MockRoom(memberUserIdAlice), membership, ); - const { container } = render(); - expect(container.getElementsByTagName("audio")).toHaveLength( - // All reactions plus the generic sound - ReactionSet.filter((r) => r.sound).length + 1, - ); -}); - -test("loads no audio elements when disabled in settings", () => { - playReactionsSound.setValue(false); - const rtcSession = new MockRTCSession( - new MockRoom(memberUserIdAlice), - membership, - ); - const { container } = render(); - expect(container.getElementsByTagName("audio")).toHaveLength(0); + render(); + expect(prefetchSounds).toHaveBeenCalledOnce(); }); test("will play an audio sound when there is a reaction", () => { - const audioIsPlaying: string[] = mockMediaPlay(); playReactionsSound.setValue(true); const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); @@ -97,12 +113,10 @@ test("will play an audio sound when there is a reaction", () => { act(() => { room.testSendReaction(memberEventAlice, chosenReaction, membership); }); - expect(audioIsPlaying).toHaveLength(1); - expect(audioIsPlaying[0]).toContain(chosenReaction.sound?.ogg); + expect(playSound).toHaveBeenCalledWith(chosenReaction.name); }); test("will play the generic audio sound when there is soundless reaction", () => { - const audioIsPlaying: string[] = mockMediaPlay(); playReactionsSound.setValue(true); const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); @@ -118,34 +132,10 @@ test("will play the generic audio sound when there is soundless reaction", () => act(() => { room.testSendReaction(memberEventAlice, chosenReaction, membership); }); - expect(audioIsPlaying).toHaveLength(1); - expect(audioIsPlaying[0]).toContain(GenericReaction.sound?.ogg); -}); - -test("will play an audio sound with the correct volume", () => { - playReactionsSound.setValue(true); - soundEffectVolumeSetting.setValue(0.5); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { getByTestId } = render(); - - // Find the first reaction with a sound effect - const chosenReaction = ReactionSet.find((r) => !!r.sound); - if (!chosenReaction) { - throw Error( - "No reactions have sounds configured, this test cannot succeed", - ); - } - act(() => { - room.testSendReaction(memberEventAlice, chosenReaction, membership); - }); - expect((getByTestId(chosenReaction.name) as HTMLAudioElement).volume).toEqual( - 0.5, - ); + expect(playSound).toHaveBeenCalledWith(GenericReaction.name); }); test("will play multiple audio sounds when there are multiple different reactions", () => { - const audioIsPlaying: string[] = mockMediaPlay(); playReactionsSound.setValue(true); const room = new MockRoom(memberUserIdAlice); @@ -164,7 +154,6 @@ test("will play multiple audio sounds when there are multiple different reaction room.testSendReaction(memberEventBob, reaction2, membership); room.testSendReaction(memberEventCharlie, reaction1, membership); }); - expect(audioIsPlaying).toHaveLength(2); - expect(audioIsPlaying[0]).toContain(reaction1.sound?.ogg); - expect(audioIsPlaying[1]).toContain(reaction2.sound?.ogg); + expect(playSound).toHaveBeenCalledWith(reaction1.name); + expect(playSound).toHaveBeenCalledWith(reaction2.name); }); diff --git a/src/room/ReactionAudioRenderer.tsx b/src/room/ReactionAudioRenderer.tsx index cc0b4a57..15bfc90f 100644 --- a/src/room/ReactionAudioRenderer.tsx +++ b/src/room/ReactionAudioRenderer.tsx @@ -5,70 +5,67 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ReactNode, useEffect, useRef } from "react"; +import { ReactNode, useDeferredValue, useEffect, useState } from "react"; import { useReactions } from "../useReactions"; -import { - playReactionsSound, - soundEffectVolumeSetting as effectSoundVolumeSetting, - useSetting, -} from "../settings/settings"; +import { playReactionsSound, useSetting } from "../settings/settings"; import { GenericReaction, ReactionSet } from "../reactions"; +import { useAudioContext } from "../useAudioContext"; +import { prefetchSounds } from "../soundUtils"; +import { useLatest } from "../useLatest"; + +const soundMap = Object.fromEntries([ + ...ReactionSet.filter((v) => v.sound !== undefined).map((v) => [ + v.name, + v.sound!, + ]), + [GenericReaction.name, GenericReaction.sound], +]); export function ReactionsAudioRenderer(): ReactNode { const { reactions } = useReactions(); const [shouldPlay] = useSetting(playReactionsSound); - const [effectSoundVolume] = useSetting(effectSoundVolumeSetting); - const audioElements = useRef>({}); + const [soundCache, setSoundCache] = useState | null>(null); + const audioEngineCtx = useAudioContext({ + sounds: soundCache, + latencyHint: "interactive", + }); + const audioEngineRef = useLatest(audioEngineCtx); + const oldReactions = useDeferredValue(reactions); useEffect(() => { - if (!audioElements.current) { + if (!shouldPlay || soundCache) { return; } + // This is fine even if we load the component multiple times, + // as the browser's cache should ensure once the media is loaded + // once that future fetches come via the cache. + setSoundCache(prefetchSounds(soundMap)); + }, [soundCache, shouldPlay]); - if (!shouldPlay) { + useEffect(() => { + if (!shouldPlay || !audioEngineRef.current) { return; } + const oldReactionSet = new Set( + Object.values(oldReactions).map((r) => r.name), + ); for (const reactionName of new Set( Object.values(reactions).map((r) => r.name), )) { - const audioElement = - audioElements.current[reactionName] ?? audioElements.current.generic; - if (audioElement?.paused) { - audioElement.volume = effectSoundVolume; - void audioElement.play(); + if (oldReactionSet.has(reactionName)) { + // Don't replay old reactions + return; + } + if (soundMap[reactionName]) { + audioEngineRef.current.playSound(reactionName); + } else { + // Fallback sounds. + audioEngineRef.current.playSound("generic"); } } - }, [audioElements, shouldPlay, reactions, effectSoundVolume]); - - // Do not render any audio elements if playback is disabled. Will save - // audio file fetches. - if (!shouldPlay) { - return null; - } - - // NOTE: We load all audio elements ahead of time to allow the cache - // to be populated, rather than risk a cache miss and have the audio - // be delayed. - return ( - <> - {[GenericReaction, ...ReactionSet].map( - (r) => - r.sound && ( - - ), - )} - - ); + }, [audioEngineRef, shouldPlay, oldReactions, reactions]); + return null; } diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 4ffcecf5..9d7eb5a1 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ChangeEvent, FC, useCallback } from "react"; +import { ChangeEvent, FC, useCallback, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { Root as Form, Text } from "@vector-im/compound-web"; @@ -83,8 +83,8 @@ export const SettingsModal: FC = ({ const devices = useMediaDevices(); useMediaDeviceNames(devices, open); - const [soundVolume, setSoundVolume] = useSetting(soundEffectVolumeSetting); + const [soundVolumeRaw, setSoundVolumeRaw] = useState(soundVolume); const audioTab: Tab = { key: "audio", @@ -107,8 +107,9 @@ export const SettingsModal: FC = ({

{t("settings.audio_tab.effect_volume_description")}

= Promise< + Record +>; + +/** + * Determine the best format we can use to play our sounds + * through. We prefer ogg support if possible, but will fall + * back to MP3. + * @returns "ogg" if the browser is likely to support it, or "mp3" otherwise. + */ +function getPreferredAudioFormat(): "ogg" | "mp3" { + const a = document.createElement("audio"); + if (a.canPlayType("audio/ogg") === "maybe") { + return "ogg"; + } + // Otherwise just assume MP3, as that has a chance of being more widely supported. + return "mp3"; +} + +const preferredFormat = getPreferredAudioFormat(); + +/** + * Prefetch sounds to be used by the AudioContext. This can + * be called outside the scope of a component to ensure the + * sounds load ahead of time. + * @param sounds A set of sound files that may be played. + * @returns A map of sound files to buffers. + */ +export async function prefetchSounds( + sounds: Record, +): PrefetchedSounds { + const buffers: Record = {}; + await Promise.all( + Object.entries(sounds).map(async ([name, file]) => { + const { mp3, ogg } = file as SoundDefinition; + // Use preferred format, fallback to ogg if no mp3 is provided. + // Load an audio file + const response = await fetch( + preferredFormat === "ogg" ? ogg : (mp3 ?? ogg), + ); + if (!response.ok) { + // If the sound doesn't load, it's not the end of the world. We won't play + // the sound when requested, but it's better than failing the whole application. + logger.warn(`Could not load sound ${name}, response was not okay`); + return; + } + // Decode it + buffers[name] = await response.arrayBuffer(); + }), + ); + return buffers as Record; +} diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 95762c3f..af8780b1 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -335,7 +335,7 @@ function findMatrixRoomMember( // must be at least 3 parts because we know the first part is a userId which must necessarily contain a colon if (parts.length < 3) { logger.warn( - "Livekit participants ID doesn't look like a userId:deviceId combination", + `Livekit participants ID (${id}) doesn't look like a userId:deviceId combination`, ); return undefined; } diff --git a/src/useAudioContext.test.tsx b/src/useAudioContext.test.tsx new file mode 100644 index 00000000..5a1afe43 --- /dev/null +++ b/src/useAudioContext.test.tsx @@ -0,0 +1,129 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { expect, test, vitest } from "vitest"; +import { FC } from "react"; +import { render } from "@testing-library/react"; +import { afterEach } from "node:test"; +import userEvent from "@testing-library/user-event"; + +import { deviceStub, MediaDevicesContext } from "./livekit/MediaDevicesContext"; +import { useAudioContext } from "./useAudioContext"; +import { soundEffectVolumeSetting } from "./settings/settings"; + +const staticSounds = Promise.resolve({ + aSound: new ArrayBuffer(0), +}); + +const TestComponent: FC = () => { + const audioCtx = useAudioContext({ + sounds: staticSounds, + latencyHint: "balanced", + }); + if (!audioCtx) { + return null; + } + return ( + <> + + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/} + + + ); +}; + +class MockAudioContext { + public static testContext: MockAudioContext; + + public constructor() { + MockAudioContext.testContext = this; + } + + public gain = vitest.mocked( + { + connect: () => {}, + gain: { + setValueAtTime: vitest.fn(), + }, + }, + true, + ); + + public setSinkId = vitest.fn().mockResolvedValue(undefined); + public decodeAudioData = vitest.fn().mockReturnValue(1); + public createBufferSource = vitest.fn().mockReturnValue( + vitest.mocked({ + connect: (v: unknown) => v, + start: () => {}, + }), + ); + public createGain = vitest.fn().mockReturnValue(this.gain); + public close = vitest.fn().mockResolvedValue(undefined); +} + +afterEach(() => { + vitest.unstubAllGlobals(); +}); + +test("can play a single sound", async () => { + const user = userEvent.setup(); + vitest.stubGlobal("AudioContext", MockAudioContext); + const { findByText } = render(); + await user.click(await findByText("Valid sound")); + expect( + MockAudioContext.testContext.createBufferSource, + ).toHaveBeenCalledOnce(); +}); +test("will ignore sounds that are not registered", async () => { + const user = userEvent.setup(); + vitest.stubGlobal("AudioContext", MockAudioContext); + const { findByText } = render(); + await user.click(await findByText("Invalid sound")); + expect( + MockAudioContext.testContext.createBufferSource, + ).not.toHaveBeenCalled(); +}); + +test("will use the correct device", () => { + vitest.stubGlobal("AudioContext", MockAudioContext); + render( + {}, + }, + videoInput: deviceStub, + startUsingDeviceNames: () => {}, + stopUsingDeviceNames: () => {}, + }} + > + + , + ); + expect( + MockAudioContext.testContext.createBufferSource, + ).not.toHaveBeenCalled(); + expect(MockAudioContext.testContext.setSinkId).toHaveBeenCalledWith( + "chosen-device", + ); +}); + +test("will use the correct volume level", async () => { + const user = userEvent.setup(); + vitest.stubGlobal("AudioContext", MockAudioContext); + soundEffectVolumeSetting.setValue(0.33); + const { findByText } = render(); + await user.click(await findByText("Valid sound")); + expect( + MockAudioContext.testContext.gain.gain.setValueAtTime, + ).toHaveBeenCalledWith(0.33, 0); +}); diff --git a/src/useAudioContext.tsx b/src/useAudioContext.tsx new file mode 100644 index 00000000..ccf4cbd5 --- /dev/null +++ b/src/useAudioContext.tsx @@ -0,0 +1,124 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { logger } from "matrix-js-sdk/src/logger"; +import { useState, useEffect } from "react"; + +import { + soundEffectVolumeSetting as effectSoundVolumeSetting, + useSetting, +} from "./settings/settings"; +import { useMediaDevices } from "./livekit/MediaDevicesContext"; +import { PrefetchedSounds } from "./soundUtils"; + +/** + * Play a sound though a given AudioContext. Will take + * care of connecting the correct buffer and gating + * through gain. + * @param volume The volume to play at. + * @param ctx The context to play through. + * @param buffer The buffer to play. + */ +function playSound( + ctx: AudioContext, + buffer: AudioBuffer, + volume: number, +): void { + const gain = ctx.createGain(); + gain.gain.setValueAtTime(volume, 0); + const src = ctx.createBufferSource(); + src.buffer = buffer; + src.connect(gain).connect(ctx.destination); + src.start(); +} + +interface Props { + /** + * The sounds to play. If no sounds should be played then + * this can be set to null, which will prevent the audio + * context from being created. + */ + sounds: PrefetchedSounds | null; + latencyHint: AudioContextLatencyCategory; +} + +interface UseAudioContext { + playSound(soundName: S): void; +} + +/** + * Add an audio context which can be used to play + * a set of preloaded sounds. + * @param props + * @returns Either an instance that can be used to play sounds, or null if not ready. + */ +export function useAudioContext( + props: Props, +): UseAudioContext | null { + const [effectSoundVolume] = useSetting(effectSoundVolumeSetting); + const devices = useMediaDevices(); + const [audioContext, setAudioContext] = useState(); + const [audioBuffers, setAudioBuffers] = useState>(); + + useEffect(() => { + const sounds = props.sounds; + if (!sounds) { + return; + } + const ctx = new AudioContext({ + // We want low latency for these effects. + latencyHint: props.latencyHint, + }); + + // We want to clone the content of our preloaded + // sound buffers into this context. The context may + // close during this process, so it's okay if it throws. + (async (): Promise => { + const buffers: Record = {}; + for (const [name, buffer] of Object.entries(await sounds)) { + const audioBuffer = await ctx.decodeAudioData(buffer.slice(0)); + buffers[name] = audioBuffer; + } + setAudioBuffers(buffers as Record); + })().catch((ex) => { + logger.debug("Failed to setup audio context", ex); + }); + + setAudioContext(ctx); + return (): void => { + void ctx.close().catch((ex) => { + logger.debug("Failed to close audio engine", ex); + }); + setAudioContext(undefined); + }; + }, [props.sounds, props.latencyHint]); + + // Update the sink ID whenever we change devices. + useEffect(() => { + if (audioContext && "setSinkId" in audioContext) { + // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/setSinkId + // @ts-expect-error - setSinkId doesn't exist yet in types, maybe because it's not supported everywhere. + audioContext.setSinkId(devices.audioOutput.selectedId).catch((ex) => { + logger.warn("Unable to change sink for audio context", ex); + }); + } + }, [audioContext, devices]); + + // Don't return a function until we're ready. + if (!audioContext || !audioBuffers) { + return null; + } + return { + playSound: (name): void => { + if (!audioBuffers[name]) { + logger.debug(`Tried to play a sound that wasn't buffered (${name})`); + return; + } + return playSound(audioContext, audioBuffers[name], effectSoundVolume); + }, + }; +} diff --git a/src/utils/test.ts b/src/utils/test.ts index dca98825..459a252e 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -27,9 +27,7 @@ import { RemoteParticipant, RemoteTrackPublication, Room as LivekitRoom, - RoomEvent, } from "livekit-client"; -import { EventEmitter } from "stream"; import { LocalUserMediaViewModel, @@ -153,33 +151,6 @@ export function mockMatrixRoom(room: Partial): MatrixRoom { return { ...mockEmitter(), ...room } as Partial as MatrixRoom; } -/** - * A mock of a Livekit Room that can emit events. - */ -export class EmittableMockLivekitRoom extends EventEmitter { - public localParticipant?: LocalParticipant; - public remoteParticipants: Map; - - public constructor(room: { - localParticipant?: LocalParticipant; - remoteParticipants: Map; - }) { - super(); - this.localParticipant = room.localParticipant; - this.remoteParticipants = room.remoteParticipants ?? new Map(); - } - - public addParticipant(remoteParticipant: RemoteParticipant): void { - this.remoteParticipants.set(remoteParticipant.identity, remoteParticipant); - this.emit(RoomEvent.ParticipantConnected, remoteParticipant); - } - - public removeParticipant(remoteParticipant: RemoteParticipant): void { - this.remoteParticipants.delete(remoteParticipant.identity); - this.emit(RoomEvent.ParticipantDisconnected, remoteParticipant); - } -} - export function mockLivekitRoom( room: Partial, { @@ -280,15 +251,6 @@ export function mockConfig(config: Partial = {}): void { }); } -export function mockMediaPlay(): string[] { - const audioIsPlaying: string[] = []; - window.HTMLMediaElement.prototype.play = async function (): Promise { - audioIsPlaying.push((this.children[0] as HTMLSourceElement).src); - return Promise.resolve(); - }; - return audioIsPlaying; -} - export class MockRTCSession extends TypedEventEmitter< MatrixRTCSessionEvent, MatrixRTCSessionEventHandlerMap diff --git a/src/utils/testReactions.tsx b/src/utils/testReactions.tsx index 84ff217b..fec3e859 100644 --- a/src/utils/testReactions.tsx +++ b/src/utils/testReactions.tsx @@ -32,7 +32,7 @@ export const TestReactionsWrapper = ({ rtcSession, children, }: PropsWithChildren<{ - rtcSession: MockRTCSession; + rtcSession: MockRTCSession | MatrixRTCSession; }>): ReactNode => { return ( @@ -203,4 +203,12 @@ export class MockRoom extends EventEmitter { }); return evt.getId()!; } + + public getMember(): void { + return; + } + + public testGetAsMatrixRoom(): Room { + return this as unknown as Room; + } } From d698705ffab0b356c72cd4cc4f308836984e00dc Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 9 Dec 2024 14:01:06 +0000 Subject: [PATCH 32/72] Configure sentry integration for netlify PR previews (#2884) --- config/config_netlify_preview.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/config_netlify_preview.json b/config/config_netlify_preview.json index de9600d4..ec1688d2 100644 --- a/config/config_netlify_preview.json +++ b/config/config_netlify_preview.json @@ -17,5 +17,9 @@ }, "rageshake": { "submit_url": "https://element.io/bugreports/submit" + }, + "sentry": { + "environment": "netlify-pr-preview", + "DSN": "https://b1e328d49be3402ba96101338989fb35@sentry.tools.element.io/41" } } From 69ed91d6d4a4b72b8784b829c81735ecd0062383 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:15:57 +0000 Subject: [PATCH 33/72] Update all non-major dependencies --- package.json | 4 +- yarn.lock | 780 ++++++++++++++++++++++++++------------------------- 2 files changed, 400 insertions(+), 384 deletions(-) diff --git a/package.json b/package.json index 71e3f9c9..f2979c9e 100644 --- a/package.json +++ b/package.json @@ -27,13 +27,13 @@ "@codecov/vite-plugin": "^1.3.0", "@fontsource/inconsolata": "^5.1.0", "@fontsource/inter": "^5.1.0", - "@formatjs/intl-durationformat": "^0.6.1", + "@formatjs/intl-durationformat": "^0.7.0", "@formatjs/intl-segmenter": "^11.7.3", "@livekit/components-core": "^0.11.0", "@livekit/components-react": "^2.0.0", "@opentelemetry/api": "^1.4.0", "@opentelemetry/core": "^1.25.1", - "@opentelemetry/exporter-trace-otlp-http": "^0.55.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.56.0", "@opentelemetry/resources": "^1.25.1", "@opentelemetry/sdk-trace-base": "^1.25.1", "@opentelemetry/sdk-trace-web": "^1.9.1", diff --git a/yarn.lock b/yarn.lock index ab40c0c0..bc6ecf79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,7 +53,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -76,9 +76,9 @@ integrity sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA== "@babel/compat-data@^7.25.9": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" - integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" + integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== "@babel/core@^7.16.5", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.26.0": version "7.26.0" @@ -101,13 +101,13 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.25.9", "@babel/generator@^7.26.0": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" - integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== +"@babel/generator@^7.25.9", "@babel/generator@^7.26.0", "@babel/generator@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" + integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== dependencies: - "@babel/parser" "^7.26.2" - "@babel/types" "^7.26.0" + "@babel/parser" "^7.26.3" + "@babel/types" "^7.26.3" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" @@ -284,13 +284,20 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.20.7", "@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": +"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.20.7": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== dependencies: "@babel/types" "^7.26.0" +"@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" + integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== + dependencies: + "@babel/types" "^7.26.3" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" @@ -903,9 +910,9 @@ esutils "^2.0.2" "@babel/preset-react@^7.22.15": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.25.9.tgz#5f473035dc2094bcfdbc7392d0766bd42dce173e" - integrity sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw== + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.26.3.tgz#7c5e028d623b4683c1f83a0bd4713b9100560caa" + integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw== dependencies: "@babel/helper-plugin-utils" "^7.25.9" "@babel/helper-validator-option" "^7.25.9" @@ -955,7 +962,7 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/traverse@^7.10.3", "@babel/traverse@^7.25.9": +"@babel/traverse@^7.10.3": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== @@ -968,7 +975,20 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.25.4", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.4.4": +"@babel/traverse@^7.25.9": + version "7.26.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" + integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.3" + "@babel/parser" "^7.26.3" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.3" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.4.4": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== @@ -976,6 +996,14 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@babel/types@^7.25.4", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" + integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -986,10 +1014,10 @@ resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.10.0.tgz#1a67ac889c2d464a3492b3e54c38f80517963b16" integrity sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag== -"@codecov/bundler-plugin-core@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.4.0.tgz#6035d8fe2a321b125c883ab77b9e6c36c9c08abd" - integrity sha512-/Rglx52KLdyqoZBW3DH2E/31c9/zWWZ4efTf+qxV0FSLb7oJ9/JZT3IBKL7f6fbVujR8PDMLIoG4Q0pmVY7LzA== +"@codecov/bundler-plugin-core@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.6.0.tgz#59da9dc464752ac4ce6f1fa142261aa42f6a8092" + integrity sha512-x2M5P1NUk5lNW5slKY3jSb6Hpuie7bKaolDyZ7oWBHvBgtAJOeU7VrutdVhaiYoiQonM65JI2UAIWtw6mup/Yw== dependencies: "@actions/core" "^1.10.1" "@actions/github" "^6.0.0" @@ -999,11 +1027,11 @@ zod "^3.22.4" "@codecov/vite-plugin@^1.3.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@codecov/vite-plugin/-/vite-plugin-1.4.0.tgz#01e4ec2a0b7c144b054ba5876bc5ab5d577a3112" - integrity sha512-4pf9rZJLR/eqeoY0QY1pgAJs/tdg1os9xjgBBWuhQ/iLYseQZ3q1qn3G8QGuaSUS7XB/Sje3BQ5qGBM1hzE8Sw== + version "1.6.0" + resolved "https://registry.yarnpkg.com/@codecov/vite-plugin/-/vite-plugin-1.6.0.tgz#5433600f1df8528d4ce693cc1ae9297b07b197d5" + integrity sha512-QwgFfF0FJMXovE/ZX33GqkBjkwUwzjPkWepwJizXiQD9emFS7iW82q1vPV9goiakJAvsCVm7Au9e7QnMBGJgvw== dependencies: - "@codecov/bundler-plugin-core" "^1.4.0" + "@codecov/bundler-plugin-core" "^1.6.0" unplugin "^1.10.1" "@csstools/cascade-layer-name-parser@^2.0.4": @@ -1666,45 +1694,46 @@ resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.0.tgz#ab629b2c662457022d2d6a29854b8dc8ba538c47" integrity sha512-zKZR3kf1G0noIes1frLfOHP5EXVVm0M7sV/l9f/AaYf+M/DId35FO4LkigWjqWYjTJZGgplhdv4cB+ssvCqr5A== -"@formatjs/ecma402-abstract@2.2.4": - version "2.2.4" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz#355e42d375678229d46dc8ad7a7139520dd03e7b" - integrity sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg== +"@formatjs/ecma402-abstract@2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz#cdeb3ffe1aeea9c4284b85b7e37e8e8615314c39" + integrity sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw== dependencies: - "@formatjs/fast-memoize" "2.2.3" - "@formatjs/intl-localematcher" "0.5.8" + "@formatjs/fast-memoize" "2.2.5" + "@formatjs/intl-localematcher" "0.5.9" + decimal.js "10" tslib "2" -"@formatjs/fast-memoize@2.2.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz#74e64109279d5244f9fc281f3ae90c407cece823" - integrity sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA== +"@formatjs/fast-memoize@2.2.5": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz#54a4a1793d773b72c372d3dcab3595149aee7880" + integrity sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g== dependencies: tslib "2" -"@formatjs/intl-durationformat@^0.6.1": - version "0.6.4" - resolved "https://registry.yarnpkg.com/@formatjs/intl-durationformat/-/intl-durationformat-0.6.4.tgz#ac6b5ab006cf2b57500cce05dd1d201352500471" - integrity sha512-kpYLechF9ZvECzzMsvikBl48GkbCEAbZJN4kG/4x0FTVZkBuOWrBlj6DghCn7YsW3Bgsr0n9E0RYO373Kg3m+Q== +"@formatjs/intl-durationformat@^0.7.0": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@formatjs/intl-durationformat/-/intl-durationformat-0.7.1.tgz#d83e6f4bf188cafac50a2a911241084bafe89524" + integrity sha512-tM/sscHRcVMVAn0qMJlmq5mf3MaqA0jSz73NT4SYBHZuZqfU0EKWjJCwZBYeNRfvO6y20Yo0RzGxom0KvSVUlA== dependencies: - "@formatjs/ecma402-abstract" "2.2.4" - "@formatjs/intl-localematcher" "0.5.8" + "@formatjs/ecma402-abstract" "2.3.1" + "@formatjs/intl-localematcher" "0.5.9" tslib "2" -"@formatjs/intl-localematcher@0.5.8": - version "0.5.8" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz#b11bbd04bd3551f7cadcb1ef1e231822d0e3c97e" - integrity sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg== +"@formatjs/intl-localematcher@0.5.9": + version "0.5.9" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz#43c6ee22be85b83340bcb09bdfed53657a2720db" + integrity sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA== dependencies: tslib "2" "@formatjs/intl-segmenter@^11.7.3": - version "11.7.4" - resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.4.tgz#f99d87ee3f98515069285438a4913681fc243252" - integrity sha512-pyHgFO86/CReKl20oK9jgaTMzSaG/nIMteMW8YuwUcS22EoMI1qbGTZ65oQ38KMT05SiHiMee2CP3WZvCi8YSQ== + version "11.7.7" + resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.7.tgz#8a5aaa316e11ca2d31b99222e6fcf1ab539b085e" + integrity sha512-610J5xz5DxtEpa16zNR89CrvA9qWHxQFkUB3FKiGao0Nwn7i8cl+oyBhuH9SvtXF9j2LUOM9VMdVCMzJkVANNw== dependencies: - "@formatjs/ecma402-abstract" "2.2.4" - "@formatjs/intl-localematcher" "0.5.8" + "@formatjs/ecma402-abstract" "2.3.1" + "@formatjs/intl-localematcher" "0.5.9" tslib "2" "@gulpjs/to-absolute-glob@^4.0.0": @@ -1929,16 +1958,16 @@ "@octokit/openapi-types" "^20.0.0" "@octokit/types@^13.0.0", "@octokit/types@^13.1.0": - version "13.6.1" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.1.tgz#432fc6c0aaae54318e5b2d3e15c22ac97fc9b15f" - integrity sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g== + version "13.6.2" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd" + integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA== dependencies: "@octokit/openapi-types" "^22.2.0" -"@opentelemetry/api-logs@0.55.0": - version "0.55.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.55.0.tgz#5cd7461820d864600250deb3803c32367a6bb2d2" - integrity sha512-3cpa+qI45VHYcA5c0bHM6VHo9gicv3p5mlLHNG3rLyjQU8b7e0st1rWtrUn3JbZ3DwwCfhKop4eQ9UuYlC6Pkg== +"@opentelemetry/api-logs@0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.56.0.tgz#68f8c51ca905c260b610c8a3c67d3f9fa3d59a45" + integrity sha512-Wr39+94UNNG3Ei9nv3pHd4AJ63gq5nSemMRpCd8fPwDL9rN3vK26lzxfH27mw16XzOSO+TpyQwBAMaLxaPWG0g== dependencies: "@opentelemetry/api" "^1.3.0" @@ -1947,94 +1976,89 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/core@1.28.0", "@opentelemetry/core@^1.25.1": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.28.0.tgz#e97290a3e36c59480ffb2287fe2713c66749274c" - integrity sha512-ZLwRMV+fNDpVmF2WYUdBHlq0eOWtEaUJSusrzjGnBt7iSRvfjFE3RXYUZJrqou/wIDWV0DwQ5KIfYe9WXg9Xqw== +"@opentelemetry/core@1.29.0", "@opentelemetry/core@^1.25.1": + version "1.29.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.29.0.tgz#a9397dfd9a8b37b2435b5e44be16d39ec1c82bd9" + integrity sha512-gmT7vAreXl0DTHD2rVZcw3+l2g84+5XiHIqdBUxXbExymPCvSsGOpiwMmn8nkiJur28STV31wnhIDrzWDPzjfA== dependencies: - "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/semantic-conventions" "1.28.0" -"@opentelemetry/exporter-trace-otlp-http@^0.55.0": - version "0.55.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.55.0.tgz#275e458aa3bd33c52d77f4357706bcfa53d27f28" - integrity sha512-lMiNic63EVHpW+eChmLD2CieDmwQBFi72+LFbh8+5hY0ShrDGrsGP/zuT5MRh7M/vM/UZYO/2A/FYd7CMQGR7A== +"@opentelemetry/exporter-trace-otlp-http@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.56.0.tgz#184bd208d68bd19c3382a9a22737200b34f7edb9" + integrity sha512-vqVuJvcwameA0r0cNrRzrZqPLB0otS+95g0XkZdiKOXUo81wYdY6r4kyrwz4nSChqTBEFm0lqi/H2OWGboOa6g== dependencies: - "@opentelemetry/core" "1.28.0" - "@opentelemetry/otlp-exporter-base" "0.55.0" - "@opentelemetry/otlp-transformer" "0.55.0" - "@opentelemetry/resources" "1.28.0" - "@opentelemetry/sdk-trace-base" "1.28.0" + "@opentelemetry/core" "1.29.0" + "@opentelemetry/otlp-exporter-base" "0.56.0" + "@opentelemetry/otlp-transformer" "0.56.0" + "@opentelemetry/resources" "1.29.0" + "@opentelemetry/sdk-trace-base" "1.29.0" -"@opentelemetry/otlp-exporter-base@0.55.0": - version "0.55.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.55.0.tgz#db17332497e4a97e4ca85d394fb91cbbcfd76d84" - integrity sha512-iHQI0Zzq3h1T6xUJTVFwmFl5Dt5y1es+fl4kM+k5T/3YvmVyeYkSiF+wHCg6oKrlUAJfk+t55kaAu3sYmt7ZYA== +"@opentelemetry/otlp-exporter-base@0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.56.0.tgz#3461fd403fbd3d366df46536a5a7dd7c7f499536" + integrity sha512-eURvv0fcmBE+KE1McUeRo+u0n18ZnUeSc7lDlW/dzlqFYasEbsztTK4v0Qf8C4vEY+aMTjPKUxBG0NX2Te3Pmw== dependencies: - "@opentelemetry/core" "1.28.0" - "@opentelemetry/otlp-transformer" "0.55.0" + "@opentelemetry/core" "1.29.0" + "@opentelemetry/otlp-transformer" "0.56.0" -"@opentelemetry/otlp-transformer@0.55.0": - version "0.55.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.55.0.tgz#316b9325983e660cb4f18cb76fa84ce1c0cdad42" - integrity sha512-kVqEfxtp6mSN2Dhpy0REo1ghP4PYhC1kMHQJ2qVlO99Pc+aigELjZDfg7/YKmL71gR6wVGIeJfiql/eXL7sQPA== +"@opentelemetry/otlp-transformer@0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.56.0.tgz#d2bae377ff2cabc0366d002ab993fcb8ea7d2700" + integrity sha512-kVkH/W2W7EpgWWpyU5VnnjIdSD7Y7FljQYObAQSKdRcejiwMj2glypZtUdfq1LTJcv4ht0jyTrw1D3CCxssNtQ== dependencies: - "@opentelemetry/api-logs" "0.55.0" - "@opentelemetry/core" "1.28.0" - "@opentelemetry/resources" "1.28.0" - "@opentelemetry/sdk-logs" "0.55.0" - "@opentelemetry/sdk-metrics" "1.28.0" - "@opentelemetry/sdk-trace-base" "1.28.0" + "@opentelemetry/api-logs" "0.56.0" + "@opentelemetry/core" "1.29.0" + "@opentelemetry/resources" "1.29.0" + "@opentelemetry/sdk-logs" "0.56.0" + "@opentelemetry/sdk-metrics" "1.29.0" + "@opentelemetry/sdk-trace-base" "1.29.0" protobufjs "^7.3.0" -"@opentelemetry/resources@1.28.0", "@opentelemetry/resources@^1.25.1": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.28.0.tgz#c8c27ae7559c817f9d117f1bf96d76f893fb29f5" - integrity sha512-cIyXSVJjGeTICENN40YSvLDAq4Y2502hGK3iN7tfdynQLKWb3XWZQEkPc+eSx47kiy11YeFAlYkEfXwR1w8kfw== +"@opentelemetry/resources@1.29.0", "@opentelemetry/resources@^1.25.1": + version "1.29.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.29.0.tgz#d170f39b2ac93d61b53d13dfcd96795181bdc372" + integrity sha512-s7mLXuHZE7RQr1wwweGcaRp3Q4UJJ0wazeGlc/N5/XSe6UyXfsh1UQGMADYeg7YwD+cEdMtU1yJAUXdnFzYzyQ== dependencies: - "@opentelemetry/core" "1.28.0" - "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/core" "1.29.0" + "@opentelemetry/semantic-conventions" "1.28.0" -"@opentelemetry/sdk-logs@0.55.0": - version "0.55.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.55.0.tgz#78844e502167723a258c75a6b4f3de3900c13ea3" - integrity sha512-TSx+Yg/d48uWW6HtjS1AD5x6WPfLhDWLl/WxC7I2fMevaiBuKCuraxTB8MDXieCNnBI24bw9ytyXrDCswFfWgA== +"@opentelemetry/sdk-logs@0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.56.0.tgz#2ce3416111d1524305f4ec92dccf9e9f9e9626cf" + integrity sha512-OS0WPBJF++R/cSl+terUjQH5PebloidB1Jbbecgg2rnCmQbTST9xsRes23bLfDQVRvmegmHqDh884h0aRdJyLw== dependencies: - "@opentelemetry/api-logs" "0.55.0" - "@opentelemetry/core" "1.28.0" - "@opentelemetry/resources" "1.28.0" + "@opentelemetry/api-logs" "0.56.0" + "@opentelemetry/core" "1.29.0" + "@opentelemetry/resources" "1.29.0" -"@opentelemetry/sdk-metrics@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.28.0.tgz#257b5295bbe9de1ad31c5e8cb43a660c25911d20" - integrity sha512-43tqMK/0BcKTyOvm15/WQ3HLr0Vu/ucAl/D84NO7iSlv6O4eOprxSHa3sUtmYkaZWHqdDJV0AHVz/R6u4JALVQ== +"@opentelemetry/sdk-metrics@1.29.0": + version "1.29.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.29.0.tgz#26b9891e47715c0caaaa4d4e8b536685e1937a06" + integrity sha512-MkVtuzDjXZaUJSuJlHn6BSXjcQlMvHcsDV7LjY4P6AJeffMa4+kIGDjzsCf6DkAh6Vqlwag5EWEam3KZOX5Drw== dependencies: - "@opentelemetry/core" "1.28.0" - "@opentelemetry/resources" "1.28.0" + "@opentelemetry/core" "1.29.0" + "@opentelemetry/resources" "1.29.0" -"@opentelemetry/sdk-trace-base@1.28.0", "@opentelemetry/sdk-trace-base@^1.25.1": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.28.0.tgz#6195dc8cd78bd74394cf54c67c5cbd8d1528516c" - integrity sha512-ceUVWuCpIao7Y5xE02Xs3nQi0tOGmMea17ecBdwtCvdo9ekmO+ijc9RFDgfifMl7XCBf41zne/1POM3LqSTZDA== +"@opentelemetry/sdk-trace-base@1.29.0", "@opentelemetry/sdk-trace-base@^1.25.1": + version "1.29.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.29.0.tgz#f48d95dae0e58e601d0596bd2e482122d2688fb8" + integrity sha512-hEOpAYLKXF3wGJpXOtWsxEtqBgde0SCv+w+jvr3/UusR4ll3QrENEGnSl1WDCyRrpqOQ5NCNOvZch9UFVa7MnQ== dependencies: - "@opentelemetry/core" "1.28.0" - "@opentelemetry/resources" "1.28.0" - "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/core" "1.29.0" + "@opentelemetry/resources" "1.29.0" + "@opentelemetry/semantic-conventions" "1.28.0" "@opentelemetry/sdk-trace-web@^1.9.1": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-web/-/sdk-trace-web-1.28.0.tgz#0b8652a05ded13308c7afd162ca2ba55ff204efd" - integrity sha512-/QOIrJc/A/caKbA9voLua4isf///cjQKB6gomEzX2fL18TBqZhIkm9k2DpjlbtrQoYCJDZ9x7Phrec22aQGpQw== + version "1.29.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-web/-/sdk-trace-web-1.29.0.tgz#0d0321b511011a0174662bec821f046a55de51e8" + integrity sha512-PQVtJ76dsZ7HYBSlgZGIuxFtnKXxNbyHzMnRUxww7V2/6V/qtQN+cvNkqwPVffrUfbvClOnejo08NezAE1y+6g== dependencies: - "@opentelemetry/core" "1.28.0" - "@opentelemetry/sdk-trace-base" "1.28.0" - "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/core" "1.29.0" + "@opentelemetry/sdk-trace-base" "1.29.0" + "@opentelemetry/semantic-conventions" "1.28.0" -"@opentelemetry/semantic-conventions@1.27.0": - version "1.27.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" - integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== - -"@opentelemetry/semantic-conventions@^1.25.1": +"@opentelemetry/semantic-conventions@1.28.0", "@opentelemetry/semantic-conventions@^1.25.1": version "1.28.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz#337fb2bca0453d0726696e745f50064411f646d6" integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA== @@ -2650,110 +2674,105 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sentry-internal/browser-utils@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.41.0.tgz#9dc30a8c88aa6e1e542e5acae29ceabd1b377cc4" - integrity sha512-nU7Bn3jEUmf1QXRUT3j2ewUBlFJpe9vnAnjqpeVPDWTsVI52BwVNcJHuE37PrGs66OZ1ZkGMfKnQk43oCAa+oQ== +"@sentry-internal/browser-utils@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.43.0.tgz#b064908a537d1cc17d8ddaf0f4c5d712557cbf40" + integrity sha512-5WhJZ3SA5sZVDBwOsChDd5JCzYcwBX7sEqBqEcm3pFru6TUihEnFIJmDIbreIyrQMwUhs3dTxnfnidgjr5z1Ag== dependencies: - "@sentry/core" "8.41.0" - "@sentry/types" "8.41.0" + "@sentry/core" "8.43.0" -"@sentry-internal/feedback@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.41.0.tgz#9c3c95e6f7738a0d00fcb89061c284baef313ba2" - integrity sha512-bw+BrSNw8abOnu/IpD8YSbYubXkkT8jyNS7TM4e4UPZMuXcbtia7/r5d7kAiUfKv/sV5PNMlZLOk+EYJeLTANg== +"@sentry-internal/feedback@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.43.0.tgz#9477b999c9bca62335eb944a6f7246a96beb0111" + integrity sha512-rcGR2kzFu4vLXBQbI9eGJwjyToyjl36O2q/UKbiZBNJ5IFtDvKRLke6jIHq/YqiHPfFGpVtq5M/lYduDfA/eaQ== dependencies: - "@sentry/core" "8.41.0" - "@sentry/types" "8.41.0" + "@sentry/core" "8.43.0" -"@sentry-internal/replay-canvas@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.41.0.tgz#9da984adc54fcd8ebe07cbbc13132fa78396dd01" - integrity sha512-lpgOBHWr1ZNxidD72A2pfoUMjIpwonOPYoQZWAHr86Oa3eIVQOyfklZlHW+gKPFl2/IEl9Lbtcke0JiDp3dkIQ== +"@sentry-internal/replay-canvas@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.43.0.tgz#f5672a08c9eb588afa0bf36f07b9f5c29b5c9920" + integrity sha512-rL8G7E1GtozH8VNalRrBQNjYDJ5ChWS/vpQI5hUG11PZfvQFXEVatLvT3uO2l0xIlHm4idTsHOSLTe/usxnogQ== dependencies: - "@sentry-internal/replay" "8.41.0" - "@sentry/core" "8.41.0" - "@sentry/types" "8.41.0" + "@sentry-internal/replay" "8.43.0" + "@sentry/core" "8.43.0" -"@sentry-internal/replay@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.41.0.tgz#b1112a52a0cf1727589ad4d42a8fac9f98f6d733" - integrity sha512-ByXEY7JI95y4Qr9fS3d28l9uuVU5Qa0HgL+xDmYElNx7CXz3Q9hFN6ibgUeC3h8BO5pDULxWNgAppl7FRY8N5w== +"@sentry-internal/replay@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.43.0.tgz#4e2e3844f52b47b16bf816d21857921bbfe85d62" + integrity sha512-geV5/zejLfGGwWHjylzrb1w8NI3U37GMG9/53nmv13FmTXUDF5XF2lh41KXFVYwvp7Ha4bd1FRQ9IU9YtBWskw== dependencies: - "@sentry-internal/browser-utils" "8.41.0" - "@sentry/core" "8.41.0" - "@sentry/types" "8.41.0" + "@sentry-internal/browser-utils" "8.43.0" + "@sentry/core" "8.43.0" -"@sentry/babel-plugin-component-annotate@2.22.6": - version "2.22.6" - resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.6.tgz#829d6caf2c95c1c46108336de4e1049e6521435e" - integrity sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ== +"@sentry/babel-plugin-component-annotate@2.22.7": + version "2.22.7" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.7.tgz#604c7e33d48528a13477e7af597c4d5fca51b8bd" + integrity sha512-aa7XKgZMVl6l04NY+3X7BP7yvQ/s8scn8KzQfTLrGRarziTlMGrsCOBQtCNWXOPEbtxAIHpZ9dsrAn5EJSivOQ== -"@sentry/browser@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.41.0.tgz#f594012e6377a92db72127ef39aee812efe3a3b7" - integrity sha512-FfAU55eYwW2lG4M3dEw2472RvHrD5YWSfHCZvuRf/4skX38kFvKghZQ+epL+CVHTzvIRHOrbj8qQK6YLTGl9ew== +"@sentry/browser@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.43.0.tgz#4eec67bc6fb278727304045b612ac392674cade6" + integrity sha512-LGvLLnfmR8+AEgFmd7Q7KHiOTiV0P1Lvio2ENDELhEqJOIiICauttibVmig+AW02qg4kMeywvleMsUYaZv2RVA== dependencies: - "@sentry-internal/browser-utils" "8.41.0" - "@sentry-internal/feedback" "8.41.0" - "@sentry-internal/replay" "8.41.0" - "@sentry-internal/replay-canvas" "8.41.0" - "@sentry/core" "8.41.0" - "@sentry/types" "8.41.0" + "@sentry-internal/browser-utils" "8.43.0" + "@sentry-internal/feedback" "8.43.0" + "@sentry-internal/replay" "8.43.0" + "@sentry-internal/replay-canvas" "8.43.0" + "@sentry/core" "8.43.0" -"@sentry/bundler-plugin-core@2.22.6": - version "2.22.6" - resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.6.tgz#a1ea1fd43700a3ece9e7db016997e79a2782b87d" - integrity sha512-1esQdgSUCww9XAntO4pr7uAM5cfGhLsgTK9MEwAKNfvpMYJi9NUTYa3A7AZmdA8V6107Lo4OD7peIPrDRbaDCg== +"@sentry/bundler-plugin-core@2.22.7": + version "2.22.7" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.7.tgz#28204a224cd1fef58d157e5beeb2493947a9bc35" + integrity sha512-ouQh5sqcB8vsJ8yTTe0rf+iaUkwmeUlGNFi35IkCFUQlWJ22qS6OfvNjOqFI19e6eGUXks0c/2ieFC4+9wJ+1g== dependencies: "@babel/core" "^7.18.5" - "@sentry/babel-plugin-component-annotate" "2.22.6" - "@sentry/cli" "^2.36.1" + "@sentry/babel-plugin-component-annotate" "2.22.7" + "@sentry/cli" "2.39.1" dotenv "^16.3.1" find-up "^5.0.0" glob "^9.3.2" magic-string "0.30.8" unplugin "1.0.1" -"@sentry/cli-darwin@2.38.1": - version "2.38.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.38.1.tgz#f6e48caaba2d9e813c8d44ce26084634d22b6b42" - integrity sha512-IHuxm072aSTAvwuHtLg065cF00Pxm2wprnrRr2lkyWp8nLOoO7DmumWZ4pjHvhB8yZXsAbM/PSxLRBoDIRDPzQ== +"@sentry/cli-darwin@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.39.1.tgz#75c338a53834b4cf72f57599f4c72ffb36cf0781" + integrity sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ== -"@sentry/cli-linux-arm64@2.38.1": - version "2.38.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.38.1.tgz#178b14747bf7070d4b17f2a3108ea0c45d02a2dc" - integrity sha512-3bj5DS4wDusL0YHwG5qeI+O19kz4N4KDDmnWqIew56MmSSAEM5B0qKk5Hivu1vRU5vPKFwVn8BVjLnKXu9idjg== +"@sentry/cli-linux-arm64@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.1.tgz#27db44700c33fcb1e8966257020b43f8494373e6" + integrity sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw== -"@sentry/cli-linux-arm@2.38.1": - version "2.38.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.38.1.tgz#d0d64ec17692792769539d33393dcf66c45b3a35" - integrity sha512-xyf4f56O4/eeirol8t1tTQw0cwF34b3v69zn6wHtKfx2lW5IEBGO+agVNdOdosnCx6j3UadgdRXUJlSyM9kx/w== +"@sentry/cli-linux-arm@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.1.tgz#451683fa9a5a60b1359d104ec71334ed16f4b63c" + integrity sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ== -"@sentry/cli-linux-i686@2.38.1": - version "2.38.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.38.1.tgz#c6cc9ac8427fbc343e0cb4cdeee97cd5998e04fb" - integrity sha512-VygJO2oTc6GfiqqmPYNpO2bW1hzszuNyn37SSmeRuuhq1/kRwD+ZQj4OmXYEASjSLg+8mDPoWOurPjHEPKNtNw== +"@sentry/cli-linux-i686@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.1.tgz#9965a81f97a94e8b6d1d15589e43fee158e35201" + integrity sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg== -"@sentry/cli-linux-x64@2.38.1": - version "2.38.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.38.1.tgz#5d53aeb00dc16a59f9ec20058375fb6ceace12c4" - integrity sha512-9SaPJK5yAGR7qGsDubTT9O7VpNQG9KIolCOov4xJU7scbmjGaFyYBm9c7ZIqbq6B+56YchPbtD0RewZC6CiF2w== +"@sentry/cli-linux-x64@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.1.tgz#31fe008b02f92769543dc9919e2a5cbc4cda7889" + integrity sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA== -"@sentry/cli-win32-i686@2.38.1": - version "2.38.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.38.1.tgz#33c2390162ee5dbd2085918edcac528a677b6119" - integrity sha512-BVUM5y+ZDBK/LqyVvt0C7oolmg8aq7PI/u04/Pp6FLRExySqwyQim0vNyL2FRjIeX1yhbk7x4Z79UjEKqJBltA== +"@sentry/cli-win32-i686@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.1.tgz#609e8790c49414011445e397130560c777850b35" + integrity sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q== -"@sentry/cli-win32-x64@2.38.1": - version "2.38.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.38.1.tgz#e2dc089ed4ea8b40cd8aef51b526c340f3ee2542" - integrity sha512-+HgsdM3LFSzUNlDpicPRdTKfr5u+nJ2C5p4aDYPb2G+qoYW+66FI4NxgWSyzJsj3nVQ8lW5/6AoMP6U5z/e/0A== +"@sentry/cli-win32-x64@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz#1a874a5570c6d162b35d9d001c96e5389d07d2cb" + integrity sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw== -"@sentry/cli@^2.36.1": - version "2.38.1" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.38.1.tgz#a3c88c26526cf6f7ba3ef64e1e22dc669024abac" - integrity sha512-XFO04nP7cn0tboMQ4ALR81QRF/6xoWAFzNld7Io6jHbaFzihqewjxAqy7pSvVPaieepUjqe7m/Ippt00kKOACg== +"@sentry/cli@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.39.1.tgz#916bb5b7567ccf7fdf94ef6cf8a2b9ab78370d29" + integrity sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -2761,42 +2780,34 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.38.1" - "@sentry/cli-linux-arm" "2.38.1" - "@sentry/cli-linux-arm64" "2.38.1" - "@sentry/cli-linux-i686" "2.38.1" - "@sentry/cli-linux-x64" "2.38.1" - "@sentry/cli-win32-i686" "2.38.1" - "@sentry/cli-win32-x64" "2.38.1" + "@sentry/cli-darwin" "2.39.1" + "@sentry/cli-linux-arm" "2.39.1" + "@sentry/cli-linux-arm64" "2.39.1" + "@sentry/cli-linux-i686" "2.39.1" + "@sentry/cli-linux-x64" "2.39.1" + "@sentry/cli-win32-i686" "2.39.1" + "@sentry/cli-win32-x64" "2.39.1" -"@sentry/core@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.41.0.tgz#e8a25cacd25fe4358f3179e85f3c2697427fe5a6" - integrity sha512-3v7u3t4LozCA5SpZY4yqUN2U3jSrkXNoLgz6L2SUUiydyCuSwXZIFEwpLJfgQyidpNDifeQbBI5E1O910XkPsA== - dependencies: - "@sentry/types" "8.41.0" +"@sentry/core@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.43.0.tgz#e96a489e87a9999199f5ac27d8860da37c1fa8b4" + integrity sha512-ktyovtjkTMNud+kC/XfqHVCoQKreIKgx/hgeRvzPwuPyd1t1KzYmRL3DBkbcWVnyOPpVTHn+RsEI1eRcVYHtvw== "@sentry/react@^8.0.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.41.0.tgz#f99c700dcbd189661d0552a17fb7b817c30c901d" - integrity sha512-/7LEWDNdICYO5s4ie8ztgpmD/GRJ1+1nHlSKvcwjf83COzT1eGvVeuYTiXFAPmXA29sY+lV1RajziwgySadjIQ== + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.43.0.tgz#ad49bd16b0b1897613ef5cbd2f0a49b2b41f98a9" + integrity sha512-PsTzLrYio/FOJU537Y5Gj9jJi7OMHEjdttsC9INUxy5062LOd8ObtHsjE0mopLaSYEwUfSROQOBZCwmISh8ByQ== dependencies: - "@sentry/browser" "8.41.0" - "@sentry/core" "8.41.0" - "@sentry/types" "8.41.0" + "@sentry/browser" "8.43.0" + "@sentry/core" "8.43.0" hoist-non-react-statics "^3.3.2" -"@sentry/types@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.41.0.tgz#db40c93bcedad26569c5dfe10a4b31253c349a10" - integrity sha512-eqdnGr9k9H++b9CjVUoTNUVahPVWeNnMy0YGkqS5+cjWWC+x43p56202oidGFmWo6702ub/xwUNH6M5PC4kq6A== - "@sentry/vite-plugin@^2.0.0": - version "2.22.6" - resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.22.6.tgz#d08a1ede05f137636d5b3c61845d24c0114f0d76" - integrity sha512-zIieP1VLWQb3wUjFJlwOAoaaJygJhXeUoGd0e/Ha2RLb2eW2S+4gjf6y6NqyY71tZ74LYVZKg/4prB6FAZSMXQ== + version "2.22.7" + resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.22.7.tgz#9b63452d1d8cd02e6ba6234395a611ae7656c67a" + integrity sha512-sYRNiNm4toQGq2BfZSJPdw36em3eQaLu+3NTFpA7Hl4g3Sp2Rt3CYObnW5bxlFEruRhxzvdyB383N9OefVZ6KA== dependencies: - "@sentry/bundler-plugin-core" "2.22.6" + "@sentry/bundler-plugin-core" "2.22.7" unplugin "1.0.1" "@snyk/github-codeowners@1.1.0": @@ -2927,9 +2938,9 @@ react-error-boundary "^3.1.0" "@testing-library/react@^16.0.0": - version "16.0.1" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.1.tgz#29c0ee878d672703f5e7579f239005e4e0faa875" - integrity sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg== + version "16.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.1.0.tgz#aa0c61398bac82eaf89776967e97de41ac742d71" + integrity sha512-Q2ToPvg0KsVL0ohND9A3zLJWcOXXcO8IDu3fj11KhNt0UlCWyFyvnCIBkd12tidB2lkiVRG8VFqdhcqhqnAQtg== dependencies: "@babel/runtime" "^7.12.5" @@ -3081,11 +3092,9 @@ "@types/node" "*" "@types/react-dom@^18.3.0": - version "18.3.1" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07" - integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ== - dependencies: - "@types/react" "*" + version "18.3.3" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.3.tgz#3654138d0da1b0c7916f6ed0dc1cc2b576d47650" + integrity sha512-uTYkxTLkYp41nq/ULXyXMtkNT1vu5fXJoqad6uTNCOGat5t9cLgF4vMNLBXsTOXpdOI44XzKPY1M5RRm0bQHuw== "@types/react-router-dom@^5.3.3": version "5.3.3" @@ -3155,15 +3164,15 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^8.0.0": - version "8.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz#ac56825bcdf3b392fc76a94b1315d4a162f201a6" - integrity sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q== + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz#0901933326aea4443b81df3f740ca7dfc45c7bea" + integrity sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.16.0" - "@typescript-eslint/type-utils" "8.16.0" - "@typescript-eslint/utils" "8.16.0" - "@typescript-eslint/visitor-keys" "8.16.0" + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/type-utils" "8.18.0" + "@typescript-eslint/utils" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" @@ -3177,14 +3186,14 @@ "@typescript-eslint/utils" "5.62.0" "@typescript-eslint/parser@^8.0.0": - version "8.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.16.0.tgz#ee5b2d6241c1ab3e2e53f03fd5a32d8e266d8e06" - integrity sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w== + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.18.0.tgz#a1c9456cbb6a089730bf1d3fc47946c5fb5fe67b" + integrity sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q== dependencies: - "@typescript-eslint/scope-manager" "8.16.0" - "@typescript-eslint/types" "8.16.0" - "@typescript-eslint/typescript-estree" "8.16.0" - "@typescript-eslint/visitor-keys" "8.16.0" + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/typescript-estree" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" debug "^4.3.4" "@typescript-eslint/scope-manager@5.62.0": @@ -3195,21 +3204,21 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@8.16.0": - version "8.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz#ebc9a3b399a69a6052f3d88174456dd399ef5905" - integrity sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg== +"@typescript-eslint/scope-manager@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz#30b040cb4557804a7e2bcc65cf8fdb630c96546f" + integrity sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw== dependencies: - "@typescript-eslint/types" "8.16.0" - "@typescript-eslint/visitor-keys" "8.16.0" + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" -"@typescript-eslint/type-utils@8.16.0": - version "8.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz#585388735f7ac390f07c885845c3d185d1b64740" - integrity sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg== +"@typescript-eslint/type-utils@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz#6f0d12cf923b6fd95ae4d877708c0adaad93c471" + integrity sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow== dependencies: - "@typescript-eslint/typescript-estree" "8.16.0" - "@typescript-eslint/utils" "8.16.0" + "@typescript-eslint/typescript-estree" "8.18.0" + "@typescript-eslint/utils" "8.18.0" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -3218,10 +3227,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@8.16.0": - version "8.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.16.0.tgz#49c92ae1b57942458ab83d9ec7ccab3005e64737" - integrity sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ== +"@typescript-eslint/types@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.18.0.tgz#3afcd30def8756bc78541268ea819a043221d5f3" + integrity sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -3236,13 +3245,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@8.16.0": - version "8.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz#9d741e56e5b13469b5190e763432ce5551a9300c" - integrity sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw== +"@typescript-eslint/typescript-estree@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz#d8ca785799fbb9c700cdff1a79c046c3e633c7f9" + integrity sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg== dependencies: - "@typescript-eslint/types" "8.16.0" - "@typescript-eslint/visitor-keys" "8.16.0" + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -3264,15 +3273,15 @@ eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/utils@8.16.0": - version "8.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.16.0.tgz#c71264c437157feaa97842809836254a6fc833c3" - integrity sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA== +"@typescript-eslint/utils@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.18.0.tgz#48f67205d42b65d895797bb7349d1be5c39a62f7" + integrity sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.16.0" - "@typescript-eslint/types" "8.16.0" - "@typescript-eslint/typescript-estree" "8.16.0" + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/typescript-estree" "8.18.0" "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" @@ -3282,12 +3291,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@8.16.0": - version "8.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz#d5086afc060b01ff7a4ecab8d49d13d5a7b07705" - integrity sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ== +"@typescript-eslint/visitor-keys@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz#7b6d33534fa808e33a19951907231ad2ea5c36dd" + integrity sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw== dependencies: - "@typescript-eslint/types" "8.16.0" + "@typescript-eslint/types" "8.18.0" eslint-visitor-keys "^4.2.0" "@ungap/structured-clone@^1.2.0": @@ -3345,9 +3354,9 @@ react-refresh "^0.14.2" "@vitest/coverage-v8@^2.0.5": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.7.tgz#63a3d246b6e516ae0746e40e5fe7feaec305a777" - integrity sha512-deQ4J+yu6nEjmEfcBndbgrRM95IZoRpV1dDVRbZhjUcgYVZz/Wc4YaLiDDt9Sy5qcikrJUZMlrUxDy7dBojebg== + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz#738527e6e79cef5004248452527e272e0df12284" + integrity sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw== dependencies: "@ampproject/remapping" "^2.3.0" "@bcoe/v8-coverage" "^0.2.3" @@ -3362,62 +3371,62 @@ test-exclude "^7.0.1" tinyrainbow "^1.2.0" -"@vitest/expect@2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.7.tgz#5ada3ec9f3060821c8f57880017a7cfb151a259b" - integrity sha512-folWk4qQDEedgUyvaZw94LIJuNLoDtY+rhKhhNy0csdwifn/pQz8EWVRnyrW3j0wMpy+xwJT8WiwiYxk+i+s7w== +"@vitest/expect@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.8.tgz#13fad0e8d5a0bf0feb675dcf1d1f1a36a1773bc1" + integrity sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw== dependencies: - "@vitest/spy" "2.1.7" - "@vitest/utils" "2.1.7" + "@vitest/spy" "2.1.8" + "@vitest/utils" "2.1.8" chai "^5.1.2" tinyrainbow "^1.2.0" -"@vitest/mocker@2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.7.tgz#424d40edd53b1c25ca00cd5c7cb335b8dcbd82d1" - integrity sha512-nKMTnuJrarFH+7llWxeLmYRldIwTY3OM1DzdytHj0f2+fah6Cyk4XbswhjOiTCnAvXsZAEoo1OaD6rneSSU+3Q== +"@vitest/mocker@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.8.tgz#51dec42ac244e949d20009249e033e274e323f73" + integrity sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA== dependencies: - "@vitest/spy" "2.1.7" + "@vitest/spy" "2.1.8" estree-walker "^3.0.3" magic-string "^0.30.12" -"@vitest/pretty-format@2.1.7", "@vitest/pretty-format@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.7.tgz#e8549976fbe5672942468f5d6aa4615666fd67de" - integrity sha512-HoqRIyfQlXPrRDB43h0lC8eHPUDPwFweMaD6t+psOvwClCC+oZZim6wPMjuoMnRdiFxXqbybg/QbuewgTwK1vA== +"@vitest/pretty-format@2.1.8", "@vitest/pretty-format@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.8.tgz#88f47726e5d0cf4ba873d50c135b02e4395e2bca" + integrity sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ== dependencies: tinyrainbow "^1.2.0" -"@vitest/runner@2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.7.tgz#83c56271adccac6779aca46c5f685df4416f38ce" - integrity sha512-MrDNpXUIXksR57qipYh068SOX4N1hVw6oVILlTlfeTyA1rp0asuljyp15IZwKqhjpWLObFj+tiNrOM4R8UnSqg== +"@vitest/runner@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.8.tgz#b0e2dd29ca49c25e9323ea2a45a5125d8729759f" + integrity sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg== dependencies: - "@vitest/utils" "2.1.7" + "@vitest/utils" "2.1.8" pathe "^1.1.2" -"@vitest/snapshot@2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.7.tgz#5f7afa9e5fd5c8444e376ccece31def1cbc8ec0c" - integrity sha512-OioIxV/xS393DKdlkRNhmtY0K37qVdCv8w1M2SlLTBSX+fNK6zgcd01VlT1nXdbKVDaB8Zb6BOfQYYoGeGTEGg== +"@vitest/snapshot@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.8.tgz#d5dc204f4b95dc8b5e468b455dfc99000047d2de" + integrity sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg== dependencies: - "@vitest/pretty-format" "2.1.7" + "@vitest/pretty-format" "2.1.8" magic-string "^0.30.12" pathe "^1.1.2" -"@vitest/spy@2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.7.tgz#0d2a8fbdf6e9e75282fa764348e50cd48019414b" - integrity sha512-e5pzIaIC0LBrb/j1FaF7HXlPJLGtltiAkwXTMqNEHALJc7USSLEwziJ+aIWTmjsWNg89zazg37h7oZITnublsQ== +"@vitest/spy@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.8.tgz#bc41af3e1e6a41ae3b67e51f09724136b88fa447" + integrity sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg== dependencies: tinyspy "^3.0.2" -"@vitest/utils@2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.7.tgz#e9ca17fcba9b3aaaf74c4e815a4148def585bfd2" - integrity sha512-7gUdvIzCCuIrMZu0WHTvDJo8C1NsUtOqmwmcS3bRHUcfHemj29wmkzLVNuWQD7WHoBD/+I7WIgrnzt7kxR54ow== +"@vitest/utils@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.8.tgz#f8ef85525f3362ebd37fd25d268745108d6ae388" + integrity sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA== dependencies: - "@vitest/pretty-format" "2.1.7" + "@vitest/pretty-format" "2.1.8" loupe "^3.1.2" tinyrainbow "^1.2.0" @@ -3893,9 +3902,9 @@ caniuse-lite@^1.0.30001646: integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== caniuse-lite@^1.0.30001669: - version "1.0.30001685" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001685.tgz#2d10d36c540a9a5d47ad6ab9e1ed5f61fdeadd8c" - integrity sha512-e/kJN1EMyHQzgcMEEgoo+YTCO1NGCmIYHk5Qk8jT6AazWemS5QFKJ5ShCJlH3GZrNIdZofcNCEwZqbMjjKzmnA== + version "1.0.30001687" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz#d0ac634d043648498eedf7a3932836beba90ebae" + integrity sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ== caseless@~0.12.0: version "0.12.0" @@ -4288,10 +4297,10 @@ debounce@^1.2.1: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.7: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== dependencies: ms "^2.1.3" @@ -4309,6 +4318,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.2: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -4319,7 +4335,7 @@ decamelize@^5.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== -decimal.js@^10.4.3: +decimal.js@10, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -4459,9 +4475,9 @@ dot-case@^3.0.4: tslib "^2.0.3" dotenv@^16.3.1: - version "16.4.5" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" - integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== eastasianwidth@^0.2.0: version "0.2.0" @@ -4478,9 +4494,9 @@ easy-table@1.2.0: wcwidth "^1.0.1" electron-to-chromium@^1.5.41: - version "1.5.67" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz#66ebd2be4a77469ac2760ef5e9e460ba9a43a845" - integrity sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ== + version "1.5.72" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz#a732805986d3a5b5fedd438ddf4616c7d78ac2df" + integrity sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw== emoji-regex@^8.0.0: version "8.0.0" @@ -4817,9 +4833,9 @@ eslint-plugin-matrix-org@^1.2.1: integrity sha512-A3cDjhG7RHwfCS8o3bOip8hSCsxtmgk2ahvqE5v/Ic2kPEZxixY6w8zLj7hFGsrRmPSEpLWqkVLt8uvQBapiQA== eslint-plugin-react-hooks@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz#72e2eefbac4b694f5324154619fee44f5f60f101" - integrity sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw== + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz#3d34e37d5770866c34b87d5b499f5f0b53bf0854" + integrity sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw== eslint-plugin-react@^7.29.4: version "7.37.2" @@ -5508,9 +5524,9 @@ https-proxy-agent@^7.0.5: debug "4" i18next-browser-languagedetector@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz#b6fdd9b43af67c47f2c26c9ba27710a1eaf31e2f" - integrity sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw== + version "8.0.2" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.2.tgz#037ca25c26877cad778f060a9e177054d9f8eaa3" + integrity sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g== dependencies: "@babel/runtime" "^7.23.2" @@ -5562,9 +5578,9 @@ ignore@^5.1.8, ignore@^5.2.0, ignore@^5.3.1: integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== immutable@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.2.tgz#bb8a987349a73efbe6b3b292a9cbaf1b530d296b" - integrity sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw== + version "5.0.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1" + integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw== import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" @@ -6038,9 +6054,9 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== knip@^5.27.2: - version "5.38.4" - resolved "https://registry.yarnpkg.com/knip/-/knip-5.38.4.tgz#cfc316c4f0fcf9224400d28229aad608e7118f68" - integrity sha512-7YyB9nhnmYhaxB/LbfBUmU67reAnnudNxTl6oBF7McB11TlfoOOgjrakZHOw0bp2ucUtyxtplhBcl7O++715nA== + version "5.39.2" + resolved "https://registry.yarnpkg.com/knip/-/knip-5.39.2.tgz#1faacd8d8ef36b509b2f6e396cce85b645abb04e" + integrity sha512-BuvuWRllLWV/r2G4m9ggNH+DZ6gouP/dhtJPXVlMbWNF++w9/EfrF6k2g7YBKCwjzCC+PXmYtpH8S2t8RjnY4Q== dependencies: "@nodelib/fs.walk" "1.2.8" "@snyk/github-codeowners" "1.1.0" @@ -6202,9 +6218,9 @@ magic-string@0.30.8: "@jridgewell/sourcemap-codec" "^1.4.15" magic-string@^0.30.12: - version "0.30.14" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.14.tgz#e9bb29870b81cfc1ec3cc656552f5a7fcbf19077" - integrity sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw== + version "0.30.15" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.15.tgz#d5474a2c4c5f35f041349edaba8a5cb02733ed3c" + integrity sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw== dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" @@ -6377,9 +6393,9 @@ node-fetch@^2.6.7: whatwg-url "^5.0.0" node-releases@^2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== normalize-package-data@^2.5.0: version "2.5.0" @@ -7006,9 +7022,9 @@ prelude-ls@^1.2.1: integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@^3.0.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.1.tgz#e211d451d6452db0a291672ca9154bc8c2579f7b" - integrity sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg== + version "3.4.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" + integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== pretty-format@^27.0.2: version "27.5.1" @@ -7130,9 +7146,9 @@ react-error-boundary@^3.1.0: "@babel/runtime" "^7.12.5" react-i18next@^15.0.0: - version "15.1.3" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.1.3.tgz#172c3905038ea4f90699a19949a0084b5641c94f" - integrity sha512-J11oA30FbM3NZegUZjn8ySK903z6PLBz/ZuBYyT1JMR0QPrW6PFXvl1WoUhortdGi9dM0m48/zJQlPskVZXgVw== + version "15.1.4" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.1.4.tgz#65c03c31a5e42202000652e163f22f23a9306a60" + integrity sha512-2tai71gmehbvl9ZIqPMqlCCkm/cbeV1G4STpmM3C8Uzo6T2l8jDvZxEVSsQKt8blP9X34iRFP/k1ROqG2296MQ== dependencies: "@babel/runtime" "^7.25.0" html-parse-stringify "^3.0.1" @@ -7573,9 +7589,9 @@ safe-regex-test@^1.0.3: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass@^1.42.1: - version "1.81.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.81.0.tgz#a9010c0599867909dfdbad057e4a6fbdd5eec941" - integrity sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA== + version "1.82.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.82.0.tgz#30da277af3d0fa6042e9ceabd0d984ed6d07df70" + integrity sha512-j4GMCTa8elGyN9A7x7bEglx0VgSpNUG4W4wNedQ33wSMdnkqQCT8HTwOaVSV4e6yQovcu/3Oc4coJP/l0xhL2Q== dependencies: chokidar "^4.0.0" immutable "^5.0.2" @@ -8471,10 +8487,10 @@ vinyl@^3.0.0, vinyl@~3.0.0: replace-ext "^2.0.0" teex "^1.0.1" -vite-node@2.1.7: - version "2.1.7" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.7.tgz#58bc9babc570ddf9cfbb2272d6d8d73ca28d139d" - integrity sha512-b/5MxSWd0ftWt1B1LHfzCw0ASzaxHztUwP0rcsBhkDSGy9ZDEDieSIjFG3I78nI9dUN0eSeD6LtuKPZGjwwpZQ== +vite-node@2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.8.tgz#9495ca17652f6f7f95ca7c4b568a235e0c8dbac5" + integrity sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg== dependencies: cac "^6.7.14" debug "^4.3.7" @@ -8527,17 +8543,17 @@ vitest-axe@^1.0.0-pre.3: lodash-es "^4.17.21" vitest@^2.0.0: - version "2.1.7" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.7.tgz#a24c8fb6e54e195fc51057740c1e4c4afd96fff5" - integrity sha512-wzJ7Wri44ufkzTZbI1lHsdHfiGdFRmnJ9qIudDQ6tknjJeHhF5QgNSSjk7KRZUU535qEiEXFJ7tSHqyzyIv0jQ== + version "2.1.8" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.8.tgz#2e6a00bc24833574d535c96d6602fb64163092fa" + integrity sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ== dependencies: - "@vitest/expect" "2.1.7" - "@vitest/mocker" "2.1.7" - "@vitest/pretty-format" "^2.1.7" - "@vitest/runner" "2.1.7" - "@vitest/snapshot" "2.1.7" - "@vitest/spy" "2.1.7" - "@vitest/utils" "2.1.7" + "@vitest/expect" "2.1.8" + "@vitest/mocker" "2.1.8" + "@vitest/pretty-format" "^2.1.8" + "@vitest/runner" "2.1.8" + "@vitest/snapshot" "2.1.8" + "@vitest/spy" "2.1.8" + "@vitest/utils" "2.1.8" chai "^5.1.2" debug "^4.3.7" expect-type "^1.1.0" @@ -8549,7 +8565,7 @@ vitest@^2.0.0: tinypool "^1.0.1" tinyrainbow "^1.2.0" vite "^5.0.0" - vite-node "2.1.7" + vite-node "2.1.8" why-is-node-running "^2.3.0" void-elements@3.1.0: @@ -8851,6 +8867,6 @@ zod-validation-error@^3.0.3: integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ== zod@^3.22.4: - version "3.23.8" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== + version "3.24.0" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.0.tgz#babb32313f7c5f4a99812feee806d186b4f76bde" + integrity sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w== From 8b19beae6808b1e6712f3a6261530bde56577f0f Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 10 Dec 2024 13:05:09 -0500 Subject: [PATCH 34/72] Fix lint errors --- src/room/useActiveFocus.ts | 2 +- src/settings/rageshake.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/room/useActiveFocus.ts b/src/room/useActiveFocus.ts index b66fe488..bdfe0d02 100644 --- a/src/room/useActiveFocus.ts +++ b/src/room/useActiveFocus.ts @@ -38,7 +38,7 @@ export function useActiveLivekitFocus( const oldestMembership = rtcSession.getOldestMembership(); logger.warn( `Got new active focus from membership: ${oldestMembership?.sender}/${oldestMembership?.deviceId}. - Updating focus (focus switch) from ${activeFocus} to ${newActiveFocus}`, + Updating focus (focus switch) from ${JSON.stringify(activeFocus)} to ${JSON.stringify(newActiveFocus)}`, ); setActiveFocus(newActiveFocus); } diff --git a/src/settings/rageshake.ts b/src/settings/rageshake.ts index 76ca0d6f..1030042f 100644 --- a/src/settings/rageshake.ts +++ b/src/settings/rageshake.ts @@ -84,6 +84,7 @@ class ConsoleLogger extends EventEmitter { // run. // Example line: // 2017-01-18T11:23:53.214Z W Failed to set badge count + // eslint-disable-next-line @typescript-eslint/no-base-to-string let line = `${ts} ${level} ${args.join(" ")}\n`; // Do some cleanup line = line.replace(/token=[a-zA-Z0-9-]+/gm, "token=xxxxx"); From da63db26d6e61839483c7f2105e2b5d9c390871c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 11 Dec 2024 09:22:12 +0000 Subject: [PATCH 35/72] Add missing full stops from end of settings descriptions (#2883) For consistency --- locales/en/app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 07dd53e3..1c1f7ecc 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -147,7 +147,7 @@ "screenshare_button_label": "Share screen", "settings": { "audio_tab": { - "effect_volume_description": "Volume for sound effects such as: joining or leaving a call, and reactions.", + "effect_volume_description": "Adjust the volume at which reactions and hand raised effects play.", "effect_volume_label": "Sound effect volume" }, "developer_settings_label": "Developer Settings", @@ -168,9 +168,9 @@ "reactions_show_label": "Show reactions", "reactions_title": "Reactions" }, - "preferences_tab_body": "Here you can configure extra options for an improved experience", + "preferences_tab_body": "Here you can configure extra options for an improved experience.", "preferences_tab_h4": "Preferences", - "preferences_tab_show_hand_raised_timer_description": "Show a timer when a participant raises their hand", + "preferences_tab_show_hand_raised_timer_description": "Show a timer when a participant raises their hand.", "preferences_tab_show_hand_raised_timer_label": "Show hand raise duration", "speaker_device_selection_label": "Speaker" }, From 45564fd629300005f3e32357afa6a25998b89d5a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 11 Dec 2024 09:23:37 +0000 Subject: [PATCH 36/72] Ignore URL params where they are not applicable for the mode (widget vs SPA) (#2882) --- src/UrlParams.test.ts | 91 +++++++++++++++++++++++++++++++++++++++++++ src/UrlParams.ts | 12 +++--- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/src/UrlParams.test.ts b/src/UrlParams.test.ts index 47428ac6..f78ffae4 100644 --- a/src/UrlParams.test.ts +++ b/src/UrlParams.test.ts @@ -100,4 +100,95 @@ describe("UrlParams", () => { expect(getUrlParams("?preload=true&widgetId=12345").preload).toBe(true); }); }); + + describe("returnToLobby", () => { + it("is true in SPA mode", () => { + expect(getUrlParams("?returnToLobby=false").returnToLobby).toBe(true); + }); + + it("defaults to false in widget mode", () => { + expect( + getUrlParams("?widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo") + .returnToLobby, + ).toBe(false); + }); + + it("respected in widget mode", () => { + expect( + getUrlParams( + "?returnToLobby=true&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo", + ).returnToLobby, + ).toBe(true); + }); + }); + + describe("userId", () => { + it("is ignored in SPA mode", () => { + expect(getUrlParams("?userId=asd").userId).toBe(null); + }); + + it("is parsed in widget mode", () => { + expect( + getUrlParams( + "?userId=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo", + ).userId, + ).toBe("asd"); + }); + }); + + describe("deviceId", () => { + it("is ignored in SPA mode", () => { + expect(getUrlParams("?deviceId=asd").deviceId).toBe(null); + }); + + it("is parsed in widget mode", () => { + expect( + getUrlParams( + "?deviceId=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo", + ).deviceId, + ).toBe("asd"); + }); + }); + + describe("baseUrl", () => { + it("is ignored in SPA mode", () => { + expect(getUrlParams("?baseUrl=asd").baseUrl).toBe(null); + }); + + it("is parsed in widget mode", () => { + expect( + getUrlParams( + "?baseUrl=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo", + ).baseUrl, + ).toBe("asd"); + }); + }); + + describe("viaServers", () => { + it("is ignored in widget mode", () => { + expect( + getUrlParams( + "?viaServers=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo", + ).viaServers, + ).toBe(null); + }); + + it("is parsed in SPA mode", () => { + expect(getUrlParams("?viaServers=asd").viaServers).toBe("asd"); + }); + }); + + describe("homeserver", () => { + it("is ignored in widget mode", () => { + expect( + getUrlParams( + "?homeserver=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo", + ).homeserver, + ).toBe(null); + }); + + it("is parsed in SPA mode", () => { + expect(getUrlParams("?homeserver=asd").homeserver).toBe("asd"); + }); + }); }); diff --git a/src/UrlParams.ts b/src/UrlParams.ts index c87b79cc..e471a9d9 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -232,10 +232,10 @@ export const getUrlParams = ( showControls: parser.getFlagParam("showControls", true), hideScreensharing: parser.getFlagParam("hideScreensharing"), e2eEnabled: parser.getFlagParam("enableE2EE", true), - userId: parser.getParam("userId"), + userId: isWidget ? parser.getParam("userId") : null, displayName: parser.getParam("displayName"), - deviceId: parser.getParam("deviceId"), - baseUrl: parser.getParam("baseUrl"), + deviceId: isWidget ? parser.getParam("deviceId") : null, + baseUrl: isWidget ? parser.getParam("baseUrl") : null, lang: parser.getParam("lang"), fonts: parser.getAllParams("font"), fontScale: Number.isNaN(fontScale) ? null : fontScale, @@ -243,10 +243,10 @@ export const getUrlParams = ( allowIceFallback: parser.getFlagParam("allowIceFallback"), perParticipantE2EE: parser.getFlagParam("perParticipantE2EE"), skipLobby: parser.getFlagParam("skipLobby"), - returnToLobby: parser.getFlagParam("returnToLobby"), + returnToLobby: isWidget ? parser.getFlagParam("returnToLobby") : true, theme: parser.getParam("theme"), - viaServers: parser.getParam("viaServers"), - homeserver: parser.getParam("homeserver"), + viaServers: !isWidget ? parser.getParam("viaServers") : null, + homeserver: !isWidget ? parser.getParam("homeserver") : null, }; }; From 8558f0349f3df4f68a268c630de429e1a2edea56 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 11 Dec 2024 09:26:55 +0000 Subject: [PATCH 37/72] Check for parentUrl when deciding whether a SPA specific url parameter should be ignored (#2881) --- src/UrlParams.test.ts | 6 +++++- src/UrlParams.ts | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/UrlParams.test.ts b/src/UrlParams.test.ts index f78ffae4..10f1386b 100644 --- a/src/UrlParams.test.ts +++ b/src/UrlParams.test.ts @@ -97,7 +97,11 @@ describe("UrlParams", () => { }); it("respected in widget mode", () => { - expect(getUrlParams("?preload=true&widgetId=12345").preload).toBe(true); + expect( + getUrlParams( + "?preload=true&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo", + ).preload, + ).toBe(true); }); }); diff --git a/src/UrlParams.ts b/src/UrlParams.ts index e471a9d9..216e5b35 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -212,11 +212,12 @@ export const getUrlParams = ( const fontScale = parseFloat(parser.getParam("fontScale") ?? ""); const widgetId = parser.getParam("widgetId"); - const isWidget = !!widgetId; + const parentUrl = parser.getParam("parentUrl"); + const isWidget = !!widgetId && !!parentUrl; return { widgetId, - parentUrl: parser.getParam("parentUrl"), + parentUrl, // NB. we don't validate roomId here as we do in getRoomIdentifierFromUrl: // what would we do if it were invalid? If the widget API says that's what From 0087e37f306c20e6f5aebb659b8c62d302c7b958 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 11 Dec 2024 09:27:55 +0000 Subject: [PATCH 38/72] Enable @typescript-eslint/consistent-type-imports lint rule (#2886) * Enable @typescript-eslint/consistent-type-imports lint rule This is to help ensure that we get proper vite/rollup lazy loading by not `import`ing more than we need to. Revert "Enable @typescript-eslint/consistent-type-imports lint rule" This reverts commit ba385fa00b7e410cc508fd5fb9fe972233ae114f. Enable @typescript-eslint/consistent-type-imports lint rule This is to help ensure that we get proper vite/rollup lazy loading by not `import`ing more than we need to. . * Format --- .eslintrc.cjs | 6 ++++ src/@types/global.d.ts | 2 +- src/@types/i18next.d.ts | 2 +- src/@types/matrix-js-sdk.d.ts | 4 +-- src/App.tsx | 4 +-- src/Avatar.test.tsx | 4 +-- src/Avatar.tsx | 10 ++++-- src/ClientContext.tsx | 4 +-- src/DisconnectedBanner.tsx | 4 +-- src/FullScreenView.tsx | 2 +- src/Header.tsx | 7 +++- src/Modal.test.tsx | 2 +- src/Modal.tsx | 2 +- src/QrCode.tsx | 2 +- src/Slider.tsx | 2 +- src/Toast.tsx | 6 ++-- src/UrlParams.ts | 2 +- src/UserMenu.tsx | 2 +- src/UserMenuContainer.tsx | 2 +- src/analytics/AnalyticsNotice.tsx | 2 +- src/analytics/PosthogAnalytics.ts | 8 +++-- src/analytics/PosthogEvents.ts | 6 ++-- src/analytics/PosthogSpanProcessor.ts | 6 ++-- src/analytics/RageshakeSpanProcessor.ts | 8 ++--- src/auth/LoginPage.tsx | 2 +- src/auth/RegisterPage.tsx | 6 ++-- src/auth/useInteractiveLogin.ts | 6 ++-- src/auth/useInteractiveRegistration.ts | 6 ++-- src/button/Button.tsx | 2 +- src/button/InviteButton.tsx | 2 +- src/button/Link.tsx | 6 ++-- src/button/LinkButton.tsx | 4 +-- src/button/ReactionToggleButton.test.tsx | 2 +- src/button/ReactionToggleButton.tsx | 12 ++++--- src/config/Config.ts | 4 +-- src/e2ee/matrixKeyProvider.test.ts | 2 +- src/e2ee/matrixKeyProvider.ts | 2 +- src/e2ee/sharedKeyManagement.ts | 2 +- src/form/Form.tsx | 2 +- src/grid/CallLayout.ts | 8 ++--- src/grid/Grid.tsx | 24 ++++++------- src/grid/GridLayout.tsx | 8 ++--- src/grid/OneOnOneLayout.tsx | 6 ++-- src/grid/SpotlightExpandedLayout.tsx | 6 ++-- src/grid/SpotlightLandscapeLayout.tsx | 4 +-- src/grid/SpotlightPortraitLayout.tsx | 6 ++-- src/grid/TileWrapper.tsx | 8 ++--- src/home/CallList.test.tsx | 6 ++-- src/home/CallList.tsx | 10 +++--- src/home/HomePage.tsx | 2 +- src/home/JoinExistingCallModal.tsx | 2 +- src/home/RegisteredView.tsx | 10 ++++-- src/home/UnauthenticatedView.tsx | 2 +- src/home/useGroupCallRooms.ts | 8 ++--- src/input/AvatarInputField.tsx | 6 ++-- src/input/Input.tsx | 8 ++--- src/livekit/MediaDevicesContext.tsx | 6 ++-- src/livekit/openIDSFU.ts | 6 ++-- src/livekit/options.ts | 6 ++-- src/livekit/useECConnectionState.ts | 8 ++--- src/livekit/useLiveKit.ts | 18 +++++----- src/otel/OTelCall.ts | 10 +++--- src/otel/OTelCallAbstractMediaStreamSpan.ts | 6 ++-- src/otel/OTelCallFeedMediaStreamSpan.ts | 8 ++--- src/otel/OTelCallMediaStreamTrackSpan.ts | 6 ++-- .../OTelCallTransceiverMediaStreamSpan.ts | 8 ++--- src/otel/OTelGroupCallMembership.ts | 36 ++++++++++--------- src/otel/ObjectFlattener.test.ts | 8 ++--- src/otel/ObjectFlattener.ts | 12 +++---- src/otel/otel.ts | 2 +- src/profile/useProfile.ts | 8 ++--- src/reactions/RaisedHandIndicator.tsx | 4 +-- src/reactions/ReactionIndicator.tsx | 2 +- src/reactions/index.ts | 2 +- src/room/AppSelectionModal.tsx | 8 ++++- src/room/CallEndedView.tsx | 10 ++++-- src/room/CallEventAudioRenderer.test.tsx | 10 +++--- src/room/CallEventAudioRenderer.tsx | 4 +-- src/room/EncryptionLock.tsx | 2 +- src/room/GroupCallView.tsx | 24 +++++++++---- src/room/InCallView.tsx | 36 ++++++++++--------- src/room/InviteModal.test.tsx | 2 +- src/room/InviteModal.tsx | 10 ++++-- src/room/LayoutToggle.tsx | 2 +- src/room/LobbyView.tsx | 10 +++--- src/room/MuteStates.test.tsx | 6 ++-- src/room/MuteStates.ts | 11 +++--- src/room/RageshakeRequestModal.tsx | 4 +-- src/room/ReactionAudioRenderer.test.tsx | 6 ++-- src/room/ReactionAudioRenderer.tsx | 2 +- src/room/ReactionsOverlay.test.tsx | 2 +- src/room/ReactionsOverlay.tsx | 2 +- src/room/RoomAuthView.tsx | 2 +- src/room/RoomPage.tsx | 4 +-- src/room/VideoPreview.tsx | 8 ++--- src/room/checkForParallelCalls.test.ts | 4 +-- src/room/checkForParallelCalls.ts | 2 +- src/room/useActiveFocus.ts | 4 +-- src/room/useJoinRule.ts | 2 +- src/room/useLoadGroupCall.ts | 8 ++--- src/room/useRoomAvatar.ts | 2 +- src/room/useRoomName.ts | 2 +- src/room/useRoomState.ts | 5 ++- src/room/useSwitchCamera.ts | 4 +-- src/rtcSessionHelper.test.ts | 2 +- src/rtcSessionHelpers.ts | 8 ++--- src/settings/DeviceSelection.tsx | 4 +-- src/settings/FeedbackSettingsTab.tsx | 2 +- src/settings/PreferencesSettingsTab.tsx | 2 +- src/settings/ProfileSettingsTab.tsx | 4 +-- src/settings/RageshakeButton.tsx | 2 +- src/settings/SettingsModal.tsx | 6 ++-- src/settings/rageshake.ts | 6 ++-- src/settings/settings.ts | 2 +- src/settings/submit-rageshake.ts | 10 +++--- src/state/CallViewModel.test.ts | 19 +++++----- src/state/CallViewModel.ts | 28 +++++++++------ src/state/GridLikeLayout.ts | 6 ++-- src/state/MediaViewModel.ts | 20 +++++------ src/state/ObservableScope.ts | 2 +- src/state/OneOnOneLayout.ts | 6 ++-- src/state/PipLayout.ts | 6 ++-- src/state/SpotlightExpandedLayout.ts | 8 ++--- src/state/TileStore.ts | 2 +- src/state/TileViewModel.ts | 4 +-- src/state/observeSpeaker.ts | 2 +- src/tabs/Tabs.tsx | 2 +- src/tile/GridTile.test.tsx | 4 +-- src/tile/GridTile.tsx | 14 ++++---- src/tile/MediaView.test.tsx | 4 +-- src/tile/MediaView.tsx | 10 +++--- src/tile/SpotlightTile.tsx | 18 +++++----- src/useAudioContext.test.tsx | 2 +- src/useAudioContext.tsx | 2 +- src/useCallViewKeyboardShortcuts.test.tsx | 8 +++-- src/useCallViewKeyboardShortcuts.ts | 8 +++-- src/useLatest.ts | 2 +- src/useMatrixRTCSessionJoinState.ts | 2 +- src/useMatrixRTCSessionMemberships.ts | 4 +-- src/useMergedRefs.ts | 2 +- src/useReactions.test.tsx | 2 +- src/useReactions.tsx | 12 +++---- src/useReactiveState.ts | 6 ++-- src/useTheme.test.ts | 2 +- src/utils/matrix.ts | 9 +++-- src/utils/observable.ts | 2 +- src/utils/spa.ts | 2 +- src/utils/test.ts | 31 ++++++++-------- src/utils/testReactions.tsx | 12 +++---- 149 files changed, 523 insertions(+), 425 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 31dccb6a..db5f3fd9 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -37,6 +37,12 @@ module.exports = { "@typescript-eslint/promise-function-async": "error", "@typescript-eslint/require-await": "error", "@typescript-eslint/await-thenable": "error", + // To help ensure that we get proper vite/rollup lazy loading (e.g. for matrix-js-sdk): + "@typescript-eslint/consistent-type-imports": [ + "error", + { fixStyle: "inline-type-imports" }, + ], + // To encourage good usage of RxJS: "rxjs/no-exposed-subjects": "error", }, settings: { diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 94a4e379..398ca4af 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import "matrix-js-sdk/src/@types/global"; import type { DurationFormat as PolyfillDurationFormat } from "@formatjs/intl-durationformat"; -import { Controls } from "../controls"; +import { type Controls } from "../controls"; declare global { interface Document { diff --git a/src/@types/i18next.d.ts b/src/@types/i18next.d.ts index 3c65e620..13210b0b 100644 --- a/src/@types/i18next.d.ts +++ b/src/@types/i18next.d.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import "i18next"; // import all namespaces (for the default language, only) -import app from "../../locales/en/app.json"; +import type app from "../../locales/en/app.json"; declare module "i18next" { interface CustomTypeOptions { diff --git a/src/@types/matrix-js-sdk.d.ts b/src/@types/matrix-js-sdk.d.ts index dc27b1ef..3ac7ef66 100644 --- a/src/@types/matrix-js-sdk.d.ts +++ b/src/@types/matrix-js-sdk.d.ts @@ -6,8 +6,8 @@ Please see LICENSE in the repository root for full details. */ import { - ElementCallReactionEventType, - ECallReactionEventContent, + type ElementCallReactionEventType, + type ECallReactionEventContent, } from "../reactions"; // Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types diff --git a/src/App.tsx b/src/App.tsx index 8d841dba..288d4c9d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, Suspense, useEffect, useState } from "react"; +import { type FC, Suspense, useEffect, useState } from "react"; import { BrowserRouter as Router, Switch, @@ -13,7 +13,7 @@ import { useLocation, } from "react-router-dom"; import * as Sentry from "@sentry/react"; -import { History } from "history"; +import { type History } from "history"; import { TooltipProvider } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/Avatar.test.tsx b/src/Avatar.test.tsx index 7eee2e90..1f3ddb04 100644 --- a/src/Avatar.test.tsx +++ b/src/Avatar.test.tsx @@ -7,8 +7,8 @@ Please see LICENSE in the repository root for full details. import { afterEach, expect, test, vi } from "vitest"; import { render, screen } from "@testing-library/react"; -import { MatrixClient } from "matrix-js-sdk/src/client"; -import { FC, PropsWithChildren } from "react"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; +import { type FC, type PropsWithChildren } from "react"; import { ClientContextProvider } from "./ClientContext"; import { Avatar } from "./Avatar"; diff --git a/src/Avatar.tsx b/src/Avatar.tsx index f3fe6cd8..dcdead7a 100644 --- a/src/Avatar.tsx +++ b/src/Avatar.tsx @@ -5,9 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { useMemo, FC, CSSProperties, useState, useEffect } from "react"; +import { + useMemo, + type FC, + type CSSProperties, + useState, + useEffect, +} from "react"; import { Avatar as CompoundAvatar } from "@vector-im/compound-web"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; import { useClientState } from "./ClientContext"; diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 7a37e750..400784b5 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { - FC, + type FC, useCallback, useEffect, useState, @@ -18,7 +18,7 @@ import { import { useHistory } from "react-router-dom"; import { logger } from "matrix-js-sdk/src/logger"; import { useTranslation } from "react-i18next"; -import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync"; +import { type ISyncStateData, type SyncState } from "matrix-js-sdk/src/sync"; import { ClientEvent, type MatrixClient } from "matrix-js-sdk/src/client"; import type { WidgetApi } from "matrix-widget-api"; diff --git a/src/DisconnectedBanner.tsx b/src/DisconnectedBanner.tsx index e317a5be..2fdb7b70 100644 --- a/src/DisconnectedBanner.tsx +++ b/src/DisconnectedBanner.tsx @@ -6,11 +6,11 @@ Please see LICENSE in the repository root for full details. */ import classNames from "classnames"; -import { FC, HTMLAttributes, ReactNode } from "react"; +import { type FC, type HTMLAttributes, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import styles from "./DisconnectedBanner.module.css"; -import { ValidClientState, useClientState } from "./ClientContext"; +import { type ValidClientState, useClientState } from "./ClientContext"; interface Props extends HTMLAttributes { children?: ReactNode; diff --git a/src/FullScreenView.tsx b/src/FullScreenView.tsx index ad66a3b8..e88f45de 100644 --- a/src/FullScreenView.tsx +++ b/src/FullScreenView.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, ReactNode, useCallback, useEffect } from "react"; +import { type FC, type ReactNode, useCallback, useEffect } from "react"; import { useLocation } from "react-router-dom"; import classNames from "classnames"; import { Trans, useTranslation } from "react-i18next"; diff --git a/src/Header.tsx b/src/Header.tsx index 69e77935..a4eb8fff 100644 --- a/src/Header.tsx +++ b/src/Header.tsx @@ -6,7 +6,12 @@ Please see LICENSE in the repository root for full details. */ import classNames from "classnames"; -import { FC, HTMLAttributes, ReactNode, forwardRef } from "react"; +import { + type FC, + type HTMLAttributes, + type ReactNode, + forwardRef, +} from "react"; import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Heading, Text } from "@vector-im/compound-web"; diff --git a/src/Modal.test.tsx b/src/Modal.test.tsx index 41bd7bbe..bb6fb0f7 100644 --- a/src/Modal.test.tsx +++ b/src/Modal.test.tsx @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { expect, test } from "vitest"; import { render } from "@testing-library/react"; -import { ReactNode, useState } from "react"; +import { type ReactNode, useState } from "react"; import { afterEach } from "node:test"; import userEvent from "@testing-library/user-event"; diff --git a/src/Modal.tsx b/src/Modal.tsx index 63d5c50a..14b6b68d 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, ReactNode, useCallback } from "react"; +import { type FC, type ReactNode, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Root as DialogRoot, diff --git a/src/QrCode.tsx b/src/QrCode.tsx index 8ad246e9..60946d70 100644 --- a/src/QrCode.tsx +++ b/src/QrCode.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useEffect, useState } from "react"; +import { type FC, useEffect, useState } from "react"; import { toDataURL } from "qrcode"; import classNames from "classnames"; import { t } from "i18next"; diff --git a/src/Slider.tsx b/src/Slider.tsx index e398fba1..86141598 100644 --- a/src/Slider.tsx +++ b/src/Slider.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useCallback } from "react"; +import { type FC, useCallback } from "react"; import { Root, Track, Range, Thumb } from "@radix-ui/react-slider"; import classNames from "classnames"; import { Tooltip } from "@vector-im/compound-web"; diff --git a/src/Toast.tsx b/src/Toast.tsx index 5b463f31..f16cfc04 100644 --- a/src/Toast.tsx +++ b/src/Toast.tsx @@ -6,9 +6,9 @@ Please see LICENSE in the repository root for full details. */ import { - ComponentType, - FC, - SVGAttributes, + type ComponentType, + type FC, + type SVGAttributes, useCallback, useEffect, } from "react"; diff --git a/src/UrlParams.ts b/src/UrlParams.ts index 216e5b35..e0aae237 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -10,7 +10,7 @@ import { useLocation } from "react-router-dom"; import { logger } from "matrix-js-sdk/src/logger"; import { Config } from "./config/Config"; -import { EncryptionSystem } from "./e2ee/sharedKeyManagement"; +import { type EncryptionSystem } from "./e2ee/sharedKeyManagement"; import { E2eeType } from "./e2ee/e2eeType"; interface RoomIdentifier { diff --git a/src/UserMenu.tsx b/src/UserMenu.tsx index 906f220f..0cb9868b 100644 --- a/src/UserMenu.tsx +++ b/src/UserMenu.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useMemo, useState } from "react"; +import { type FC, useMemo, useState } from "react"; import { useLocation } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Menu, MenuItem } from "@vector-im/compound-web"; diff --git a/src/UserMenuContainer.tsx b/src/UserMenuContainer.tsx index e73f2780..2695552d 100644 --- a/src/UserMenuContainer.tsx +++ b/src/UserMenuContainer.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useCallback, useState } from "react"; +import { type FC, useCallback, useState } from "react"; import { useHistory, useLocation } from "react-router-dom"; import { useClientLegacy } from "./ClientContext"; diff --git a/src/analytics/AnalyticsNotice.tsx b/src/analytics/AnalyticsNotice.tsx index 9ba78f0d..9725d596 100644 --- a/src/analytics/AnalyticsNotice.tsx +++ b/src/analytics/AnalyticsNotice.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC } from "react"; +import { type FC } from "react"; import { Trans } from "react-i18next"; import { ExternalLink } from "../button/Link"; diff --git a/src/analytics/PosthogAnalytics.ts b/src/analytics/PosthogAnalytics.ts index 0df0ee32..5124e711 100644 --- a/src/analytics/PosthogAnalytics.ts +++ b/src/analytics/PosthogAnalytics.ts @@ -5,9 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import posthog, { CaptureOptions, PostHog, Properties } from "posthog-js"; +import posthog, { + type CaptureOptions, + type PostHog, + type Properties, +} from "posthog-js"; import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { Buffer } from "buffer"; import { widget } from "../widget"; diff --git a/src/analytics/PosthogEvents.ts b/src/analytics/PosthogEvents.ts index ca086dc2..2e5744d2 100644 --- a/src/analytics/PosthogEvents.ts +++ b/src/analytics/PosthogEvents.ts @@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { DisconnectReason } from "livekit-client"; +import { type DisconnectReason } from "livekit-client"; import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; import { - IPosthogEvent, + type IPosthogEvent, PosthogAnalytics, RegistrationType, } from "./PosthogAnalytics"; diff --git a/src/analytics/PosthogSpanProcessor.ts b/src/analytics/PosthogSpanProcessor.ts index 102de159..c03fcab9 100644 --- a/src/analytics/PosthogSpanProcessor.ts +++ b/src/analytics/PosthogSpanProcessor.ts @@ -6,9 +6,9 @@ Please see LICENSE in the repository root for full details. */ import { - SpanProcessor, - ReadableSpan, - Span, + type SpanProcessor, + type ReadableSpan, + type Span, } from "@opentelemetry/sdk-trace-base"; import { hrTimeToMilliseconds } from "@opentelemetry/core"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/analytics/RageshakeSpanProcessor.ts b/src/analytics/RageshakeSpanProcessor.ts index ee547e29..df42641e 100644 --- a/src/analytics/RageshakeSpanProcessor.ts +++ b/src/analytics/RageshakeSpanProcessor.ts @@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { AttributeValue, Attributes } from "@opentelemetry/api"; +import { type AttributeValue, type Attributes } from "@opentelemetry/api"; import { hrTimeToMicroseconds } from "@opentelemetry/core"; import { - SpanProcessor, - ReadableSpan, - Span, + type SpanProcessor, + type ReadableSpan, + type Span, } from "@opentelemetry/sdk-trace-base"; const dumpAttributes = ( diff --git a/src/auth/LoginPage.tsx b/src/auth/LoginPage.tsx index e4aede09..11b831cd 100644 --- a/src/auth/LoginPage.tsx +++ b/src/auth/LoginPage.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, FormEvent, useCallback, useRef, useState } from "react"; +import { type FC, type FormEvent, useCallback, useRef, useState } from "react"; import { useHistory, useLocation } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; import { Button } from "@vector-im/compound-web"; diff --git a/src/auth/RegisterPage.tsx b/src/auth/RegisterPage.tsx index 392f8a7a..b12ff2ca 100644 --- a/src/auth/RegisterPage.tsx +++ b/src/auth/RegisterPage.tsx @@ -6,9 +6,9 @@ Please see LICENSE in the repository root for full details. */ import { - ChangeEvent, - FC, - FormEvent, + type ChangeEvent, + type FC, + type FormEvent, useCallback, useEffect, useRef, diff --git a/src/auth/useInteractiveLogin.ts b/src/auth/useInteractiveLogin.ts index 2bd15acb..8a70dee2 100644 --- a/src/auth/useInteractiveLogin.ts +++ b/src/auth/useInteractiveLogin.ts @@ -9,12 +9,12 @@ import { useCallback } from "react"; import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth"; import { createClient, - LoginResponse, - MatrixClient, + type LoginResponse, + type MatrixClient, } from "matrix-js-sdk/src/matrix"; import { initClient } from "../utils/matrix"; -import { Session } from "../ClientContext"; +import { type Session } from "../ClientContext"; /** * This provides the login method to login using user credentials. * @param oldClient If there is an already authenticated client it should be passed to this hook diff --git a/src/auth/useInteractiveRegistration.ts b/src/auth/useInteractiveRegistration.ts index 2c272cb1..d6568ede 100644 --- a/src/auth/useInteractiveRegistration.ts +++ b/src/auth/useInteractiveRegistration.ts @@ -9,13 +9,13 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth"; import { createClient, - MatrixClient, - RegisterResponse, + type MatrixClient, + type RegisterResponse, } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { initClient } from "../utils/matrix"; -import { Session } from "../ClientContext"; +import { type Session } from "../ClientContext"; import { Config } from "../config/Config"; import { widget } from "../widget"; diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 5c85ddbf..c4fb2db7 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -4,7 +4,7 @@ Copyright 2022-2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ComponentPropsWithoutRef, FC } from "react"; +import { type ComponentPropsWithoutRef, type FC } from "react"; import classNames from "classnames"; import { useTranslation } from "react-i18next"; import { Button as CpdButton, Tooltip } from "@vector-im/compound-web"; diff --git a/src/button/InviteButton.tsx b/src/button/InviteButton.tsx index 874c1046..bbd023f5 100644 --- a/src/button/InviteButton.tsx +++ b/src/button/InviteButton.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ComponentPropsWithoutRef, FC } from "react"; +import { type ComponentPropsWithoutRef, type FC } from "react"; import { Button } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; import { UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; diff --git a/src/button/Link.tsx b/src/button/Link.tsx index 68c4dd13..829cbdc8 100644 --- a/src/button/Link.tsx +++ b/src/button/Link.tsx @@ -6,15 +6,15 @@ Please see LICENSE in the repository root for full details. */ import { - ComponentPropsWithoutRef, + type ComponentPropsWithoutRef, forwardRef, - MouseEvent, + type MouseEvent, useCallback, useMemo, } from "react"; import { Link as CpdLink } from "@vector-im/compound-web"; import { useHistory } from "react-router-dom"; -import { createPath, LocationDescriptor, Path } from "history"; +import { createPath, type LocationDescriptor, type Path } from "history"; import classNames from "classnames"; import { useLatest } from "../useLatest"; diff --git a/src/button/LinkButton.tsx b/src/button/LinkButton.tsx index 5e231a49..b0e733b0 100644 --- a/src/button/LinkButton.tsx +++ b/src/button/LinkButton.tsx @@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ComponentPropsWithoutRef, forwardRef } from "react"; +import { type ComponentPropsWithoutRef, forwardRef } from "react"; import { Button } from "@vector-im/compound-web"; -import { LocationDescriptor } from "history"; +import { type LocationDescriptor } from "history"; import { useLink } from "./Link"; diff --git a/src/button/ReactionToggleButton.test.tsx b/src/button/ReactionToggleButton.test.tsx index a1498304..72437825 100644 --- a/src/button/ReactionToggleButton.test.tsx +++ b/src/button/ReactionToggleButton.test.tsx @@ -9,7 +9,7 @@ import { render } from "@testing-library/react"; import { expect, test } from "vitest"; import { TooltipProvider } from "@vector-im/compound-web"; import { userEvent } from "@testing-library/user-event"; -import { ReactNode } from "react"; +import { type ReactNode } from "react"; import { MockRoom, diff --git a/src/button/ReactionToggleButton.tsx b/src/button/ReactionToggleButton.tsx index b1d6ec3e..7f231d30 100644 --- a/src/button/ReactionToggleButton.tsx +++ b/src/button/ReactionToggleButton.tsx @@ -13,9 +13,9 @@ import { ReactionSolidIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { - ComponentPropsWithoutRef, - FC, - ReactNode, + type ComponentPropsWithoutRef, + type FC, + type ReactNode, useCallback, useEffect, useMemo, @@ -27,7 +27,11 @@ import classNames from "classnames"; import { useReactions } from "../useReactions"; import styles from "./ReactionToggleButton.module.css"; -import { ReactionOption, ReactionSet, ReactionsRowSize } from "../reactions"; +import { + type ReactionOption, + ReactionSet, + ReactionsRowSize, +} from "../reactions"; import { Modal } from "../Modal"; interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> { diff --git a/src/config/Config.ts b/src/config/Config.ts index e3c43bf1..27a8cb7f 100644 --- a/src/config/Config.ts +++ b/src/config/Config.ts @@ -10,8 +10,8 @@ import { merge } from "lodash-es"; import { getUrlParams } from "../UrlParams"; import { DEFAULT_CONFIG, - ConfigOptions, - ResolvedConfigOptions, + type ConfigOptions, + type ResolvedConfigOptions, } from "./ConfigOptions"; export class Config { diff --git a/src/e2ee/matrixKeyProvider.test.ts b/src/e2ee/matrixKeyProvider.test.ts index e5b4015f..df4c6009 100644 --- a/src/e2ee/matrixKeyProvider.test.ts +++ b/src/e2ee/matrixKeyProvider.test.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { describe, expect, test, vi } from "vitest"; import { - MatrixRTCSession, + type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/src/matrixrtc"; import { KeyProviderEvent } from "livekit-client"; diff --git a/src/e2ee/matrixKeyProvider.ts b/src/e2ee/matrixKeyProvider.ts index 6cbecd19..2d269bae 100644 --- a/src/e2ee/matrixKeyProvider.ts +++ b/src/e2ee/matrixKeyProvider.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { BaseKeyProvider, createKeyMaterialFromBuffer } from "livekit-client"; import { logger } from "matrix-js-sdk/src/logger"; import { - MatrixRTCSession, + type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; diff --git a/src/e2ee/sharedKeyManagement.ts b/src/e2ee/sharedKeyManagement.ts index 2b67ec4a..7936de8b 100644 --- a/src/e2ee/sharedKeyManagement.ts +++ b/src/e2ee/sharedKeyManagement.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { useEffect, useMemo } from "react"; import { setLocalStorageItem, useLocalStorage } from "../useLocalStorage"; -import { UrlParams, getUrlParams, useUrlParams } from "../UrlParams"; +import { type UrlParams, getUrlParams, useUrlParams } from "../UrlParams"; import { E2eeType } from "./e2eeType"; import { useClient } from "../ClientContext"; diff --git a/src/form/Form.tsx b/src/form/Form.tsx index 03291b79..49cd1b65 100644 --- a/src/form/Form.tsx +++ b/src/form/Form.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import classNames from "classnames"; -import { FormEventHandler, forwardRef, ReactNode } from "react"; +import { type FormEventHandler, forwardRef, type ReactNode } from "react"; import styles from "./Form.module.css"; diff --git a/src/grid/CallLayout.ts b/src/grid/CallLayout.ts index 895af23f..e05cd025 100644 --- a/src/grid/CallLayout.ts +++ b/src/grid/CallLayout.ts @@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { BehaviorSubject, Observable } from "rxjs"; -import { ComponentType } from "react"; +import { type BehaviorSubject, type Observable } from "rxjs"; +import { type ComponentType } from "react"; -import { LayoutProps } from "./Grid"; -import { TileViewModel } from "../state/TileViewModel"; +import { type LayoutProps } from "./Grid"; +import { type TileViewModel } from "../state/TileViewModel"; export interface Bounds { width: number; diff --git a/src/grid/Grid.tsx b/src/grid/Grid.tsx index 2983357b..411d9d08 100644 --- a/src/grid/Grid.tsx +++ b/src/grid/Grid.tsx @@ -6,21 +6,21 @@ Please see LICENSE in the repository root for full details. */ import { - SpringRef, - TransitionFn, - animated, + type SpringRef, + type TransitionFn, + type animated, useTransition, } from "@react-spring/web"; -import { EventTypes, Handler, useScroll } from "@use-gesture/react"; +import { type EventTypes, type Handler, useScroll } from "@use-gesture/react"; import { - CSSProperties, - ComponentProps, - ComponentType, - Dispatch, - FC, - LegacyRef, - ReactNode, - SetStateAction, + type CSSProperties, + type ComponentProps, + type ComponentType, + type Dispatch, + type FC, + type LegacyRef, + type ReactNode, + type SetStateAction, createContext, forwardRef, memo, diff --git a/src/grid/GridLayout.tsx b/src/grid/GridLayout.tsx index 3dc3bef1..45aecd85 100644 --- a/src/grid/GridLayout.tsx +++ b/src/grid/GridLayout.tsx @@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { CSSProperties, forwardRef, useCallback, useMemo } from "react"; +import { type CSSProperties, forwardRef, useCallback, useMemo } from "react"; import { distinctUntilChanged } from "rxjs"; import { useObservableEagerState } from "observable-hooks"; -import { GridLayout as GridLayoutModel } from "../state/CallViewModel"; +import { type GridLayout as GridLayoutModel } from "../state/CallViewModel"; import styles from "./GridLayout.module.css"; import { useInitial } from "../useInitial"; -import { CallLayout, arrangeTiles } from "./CallLayout"; -import { DragCallback, useUpdateLayout } from "./Grid"; +import { type CallLayout, arrangeTiles } from "./CallLayout"; +import { type DragCallback, useUpdateLayout } from "./Grid"; interface GridCSSProperties extends CSSProperties { "--gap": string; diff --git a/src/grid/OneOnOneLayout.tsx b/src/grid/OneOnOneLayout.tsx index 03ff5b32..e841a686 100644 --- a/src/grid/OneOnOneLayout.tsx +++ b/src/grid/OneOnOneLayout.tsx @@ -9,10 +9,10 @@ import { forwardRef, useCallback, useMemo } from "react"; import { useObservableEagerState } from "observable-hooks"; import classNames from "classnames"; -import { OneOnOneLayout as OneOnOneLayoutModel } from "../state/CallViewModel"; -import { CallLayout, arrangeTiles } from "./CallLayout"; +import { type OneOnOneLayout as OneOnOneLayoutModel } from "../state/CallViewModel"; +import { type CallLayout, arrangeTiles } from "./CallLayout"; import styles from "./OneOnOneLayout.module.css"; -import { DragCallback, useUpdateLayout } from "./Grid"; +import { type DragCallback, useUpdateLayout } from "./Grid"; /** * An implementation of the "one-on-one" layout, in which the remote participant diff --git a/src/grid/SpotlightExpandedLayout.tsx b/src/grid/SpotlightExpandedLayout.tsx index 08495036..371930ce 100644 --- a/src/grid/SpotlightExpandedLayout.tsx +++ b/src/grid/SpotlightExpandedLayout.tsx @@ -8,9 +8,9 @@ Please see LICENSE in the repository root for full details. import { forwardRef, useCallback } from "react"; import { useObservableEagerState } from "observable-hooks"; -import { SpotlightExpandedLayout as SpotlightExpandedLayoutModel } from "../state/CallViewModel"; -import { CallLayout } from "./CallLayout"; -import { DragCallback, useUpdateLayout } from "./Grid"; +import { type SpotlightExpandedLayout as SpotlightExpandedLayoutModel } from "../state/CallViewModel"; +import { type CallLayout } from "./CallLayout"; +import { type DragCallback, useUpdateLayout } from "./Grid"; import styles from "./SpotlightExpandedLayout.module.css"; /** diff --git a/src/grid/SpotlightLandscapeLayout.tsx b/src/grid/SpotlightLandscapeLayout.tsx index b9e6b289..8596aec8 100644 --- a/src/grid/SpotlightLandscapeLayout.tsx +++ b/src/grid/SpotlightLandscapeLayout.tsx @@ -9,8 +9,8 @@ import { forwardRef } from "react"; import { useObservableEagerState } from "observable-hooks"; import classNames from "classnames"; -import { CallLayout } from "./CallLayout"; -import { SpotlightLandscapeLayout as SpotlightLandscapeLayoutModel } from "../state/CallViewModel"; +import { type CallLayout } from "./CallLayout"; +import { type SpotlightLandscapeLayout as SpotlightLandscapeLayoutModel } from "../state/CallViewModel"; import styles from "./SpotlightLandscapeLayout.module.css"; import { useUpdateLayout } from "./Grid"; diff --git a/src/grid/SpotlightPortraitLayout.tsx b/src/grid/SpotlightPortraitLayout.tsx index e617160e..beeca3e6 100644 --- a/src/grid/SpotlightPortraitLayout.tsx +++ b/src/grid/SpotlightPortraitLayout.tsx @@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { CSSProperties, forwardRef } from "react"; +import { type CSSProperties, forwardRef } from "react"; import { useObservableEagerState } from "observable-hooks"; import classNames from "classnames"; -import { CallLayout, arrangeTiles } from "./CallLayout"; -import { SpotlightPortraitLayout as SpotlightPortraitLayoutModel } from "../state/CallViewModel"; +import { type CallLayout, arrangeTiles } from "./CallLayout"; +import { type SpotlightPortraitLayout as SpotlightPortraitLayoutModel } from "../state/CallViewModel"; import styles from "./SpotlightPortraitLayout.module.css"; import { useUpdateLayout } from "./Grid"; diff --git a/src/grid/TileWrapper.tsx b/src/grid/TileWrapper.tsx index aeb581fe..a2eebd43 100644 --- a/src/grid/TileWrapper.tsx +++ b/src/grid/TileWrapper.tsx @@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ComponentType, memo, RefObject, useRef } from "react"; -import { EventTypes, Handler, useDrag } from "@use-gesture/react"; -import { SpringValue } from "@react-spring/web"; +import { type ComponentType, memo, type RefObject, useRef } from "react"; +import { type EventTypes, type Handler, useDrag } from "@use-gesture/react"; +import { type SpringValue } from "@react-spring/web"; import classNames from "classnames"; -import { TileProps } from "./Grid"; +import { type TileProps } from "./Grid"; import styles from "./TileWrapper.module.css"; interface Props { diff --git a/src/home/CallList.test.tsx b/src/home/CallList.test.tsx index cd9e38d1..5e5a3439 100644 --- a/src/home/CallList.test.tsx +++ b/src/home/CallList.test.tsx @@ -5,13 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { render, RenderResult } from "@testing-library/react"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { render, type RenderResult } from "@testing-library/react"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { MemoryRouter } from "react-router-dom"; import { describe, expect, it } from "vitest"; import { CallList } from "../../src/home/CallList"; -import { GroupCallRoom } from "../../src/home/useGroupCallRooms"; +import { type GroupCallRoom } from "../../src/home/useGroupCallRooms"; describe("CallList", () => { const renderComponent = (rooms: GroupCallRoom[]): RenderResult => { diff --git a/src/home/CallList.tsx b/src/home/CallList.tsx index 72b7356a..12bfae45 100644 --- a/src/home/CallList.tsx +++ b/src/home/CallList.tsx @@ -6,10 +6,10 @@ Please see LICENSE in the repository root for full details. */ import { Link } from "react-router-dom"; -import { MatrixClient } from "matrix-js-sdk/src/client"; -import { RoomMember } from "matrix-js-sdk/src/models/room-member"; -import { Room } from "matrix-js-sdk/src/models/room"; -import { FC, useCallback, MouseEvent, useState } from "react"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; +import { type RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { type Room } from "matrix-js-sdk/src/models/room"; +import { type FC, useCallback, type MouseEvent, useState } from "react"; import { useTranslation } from "react-i18next"; import { IconButton, Text } from "@vector-im/compound-web"; import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; @@ -18,7 +18,7 @@ import classNames from "classnames"; import { Avatar, Size } from "../Avatar"; import styles from "./CallList.module.css"; import { getRelativeRoomUrl } from "../utils/matrix"; -import { GroupCallRoom } from "./useGroupCallRooms"; +import { type GroupCallRoom } from "./useGroupCallRooms"; import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement"; interface CallListProps { diff --git a/src/home/HomePage.tsx b/src/home/HomePage.tsx index 74575494..9340ecc0 100644 --- a/src/home/HomePage.tsx +++ b/src/home/HomePage.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { useTranslation } from "react-i18next"; -import { FC } from "react"; +import { type FC } from "react"; import { useClientState } from "../ClientContext"; import { ErrorView, LoadingView } from "../FullScreenView"; diff --git a/src/home/JoinExistingCallModal.tsx b/src/home/JoinExistingCallModal.tsx index 6de3d2a9..3f2c3902 100644 --- a/src/home/JoinExistingCallModal.tsx +++ b/src/home/JoinExistingCallModal.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { useTranslation } from "react-i18next"; -import { FC, MouseEvent } from "react"; +import { type FC, type MouseEvent } from "react"; import { Button } from "@vector-im/compound-web"; import { Modal } from "../Modal"; diff --git a/src/home/RegisteredView.tsx b/src/home/RegisteredView.tsx index db242414..7a44ebb6 100644 --- a/src/home/RegisteredView.tsx +++ b/src/home/RegisteredView.tsx @@ -5,9 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { useState, useCallback, FormEvent, FormEventHandler, FC } from "react"; +import { + useState, + useCallback, + type FormEvent, + type FormEventHandler, + type FC, +} from "react"; import { useHistory } from "react-router-dom"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; import { useTranslation } from "react-i18next"; import { Heading, Text } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/home/UnauthenticatedView.tsx b/src/home/UnauthenticatedView.tsx index daafa9f8..0e9be675 100644 --- a/src/home/UnauthenticatedView.tsx +++ b/src/home/UnauthenticatedView.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useCallback, useState, FormEventHandler } from "react"; +import { type FC, useCallback, useState, type FormEventHandler } from "react"; import { useHistory } from "react-router-dom"; import { randomString } from "matrix-js-sdk/src/randomstring"; import { Trans, useTranslation } from "react-i18next"; diff --git a/src/home/useGroupCallRooms.ts b/src/home/useGroupCallRooms.ts index 3946b51b..73464987 100644 --- a/src/home/useGroupCallRooms.ts +++ b/src/home/useGroupCallRooms.ts @@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { MatrixClient } from "matrix-js-sdk/src/client"; -import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; -import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; +import { type Room, RoomEvent } from "matrix-js-sdk/src/models/room"; +import { type RoomMember } from "matrix-js-sdk/src/models/room-member"; import { useState, useEffect } from "react"; import { EventTimeline, EventType, JoinRule } from "matrix-js-sdk/src/matrix"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager"; import { KnownMembership } from "matrix-js-sdk/src/types"; diff --git a/src/input/AvatarInputField.tsx b/src/input/AvatarInputField.tsx index 84eb48ac..a4bccb27 100644 --- a/src/input/AvatarInputField.tsx +++ b/src/input/AvatarInputField.tsx @@ -6,13 +6,13 @@ Please see LICENSE in the repository root for full details. */ import { - AllHTMLAttributes, + type AllHTMLAttributes, useEffect, useCallback, useState, - ChangeEvent, + type ChangeEvent, useRef, - FC, + type FC, } from "react"; import classNames from "classnames"; import { useTranslation } from "react-i18next"; diff --git a/src/input/Input.tsx b/src/input/Input.tsx index 77003e5a..761988ad 100644 --- a/src/input/Input.tsx +++ b/src/input/Input.tsx @@ -6,11 +6,11 @@ Please see LICENSE in the repository root for full details. */ import { - ChangeEvent, - FC, - ForwardedRef, + type ChangeEvent, + type FC, + type ForwardedRef, forwardRef, - ReactNode, + type ReactNode, useId, } from "react"; import classNames from "classnames"; diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index d19840ef..f25761f9 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { - FC, + type FC, createContext, useCallback, useContext, @@ -16,7 +16,7 @@ import { useState, } from "react"; import { createMediaDeviceObserver } from "@livekit/components-core"; -import { Observable } from "rxjs"; +import { type Observable } from "rxjs"; import { logger } from "matrix-js-sdk/src/logger"; import { @@ -24,7 +24,7 @@ import { audioInput as audioInputSetting, audioOutput as audioOutputSetting, videoInput as videoInputSetting, - Setting, + type Setting, } from "../settings/settings"; import { isFirefox } from "../Platform"; diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index 3c77db2f..ab696d4e 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { IOpenIDToken, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { type IOpenIDToken, type MatrixClient } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { useEffect, useState } from "react"; -import { LivekitFocus } from "matrix-js-sdk/src/matrixrtc/LivekitFocus"; +import { type LivekitFocus } from "matrix-js-sdk/src/matrixrtc/LivekitFocus"; import { useActiveLivekitFocus } from "../room/useActiveFocus"; diff --git a/src/livekit/options.ts b/src/livekit/options.ts index f7fe03ff..4f138585 100644 --- a/src/livekit/options.ts +++ b/src/livekit/options.ts @@ -8,10 +8,10 @@ Please see LICENSE in the repository root for full details. import { AudioPresets, DefaultReconnectPolicy, - RoomOptions, + type RoomOptions, ScreenSharePresets, - TrackPublishDefaults, - VideoPreset, + type TrackPublishDefaults, + type VideoPreset, VideoPresets, } from "livekit-client"; diff --git a/src/livekit/useECConnectionState.ts b/src/livekit/useECConnectionState.ts index 60c5b9bb..a99aa2e1 100644 --- a/src/livekit/useECConnectionState.ts +++ b/src/livekit/useECConnectionState.ts @@ -6,10 +6,10 @@ Please see LICENSE in the repository root for full details. */ import { - AudioCaptureOptions, + type AudioCaptureOptions, ConnectionState, - LocalTrack, - Room, + type LocalTrack, + type Room, RoomEvent, Track, } from "livekit-client"; @@ -17,7 +17,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import * as Sentry from "@sentry/react"; -import { SFUConfig, sfuConfigEquals } from "./openIDSFU"; +import { type SFUConfig, sfuConfigEquals } from "./openIDSFU"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; declare global { diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index 4645fab7..1bfe01d1 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -7,32 +7,32 @@ Please see LICENSE in the repository root for full details. import { ConnectionState, - E2EEOptions, + type E2EEOptions, ExternalE2EEKeyProvider, Room, - RoomOptions, + type RoomOptions, Track, } from "livekit-client"; import { useEffect, useMemo, useRef } from "react"; import E2EEWorker from "livekit-client/e2ee-worker?worker"; import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { defaultLiveKitOptions } from "./options"; -import { SFUConfig } from "./openIDSFU"; -import { MuteStates } from "../room/MuteStates"; +import { type SFUConfig } from "./openIDSFU"; +import { type MuteStates } from "../room/MuteStates"; import { - MediaDevice, - MediaDevices, + type MediaDevice, + type MediaDevices, useMediaDevices, } from "./MediaDevicesContext"; import { - ECConnectionState, + type ECConnectionState, useECConnectionState, } from "./useECConnectionState"; import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider"; import { E2eeType } from "../e2ee/e2eeType"; -import { EncryptionSystem } from "../e2ee/sharedKeyManagement"; +import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; interface UseLivekitResult { livekitRoom?: Room; diff --git a/src/otel/OTelCall.ts b/src/otel/OTelCall.ts index 586d410c..1bc349d3 100644 --- a/src/otel/OTelCall.ts +++ b/src/otel/OTelCall.ts @@ -5,17 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { Span } from "@opentelemetry/api"; -import { MatrixCall } from "matrix-js-sdk/src/matrix"; +import { type Span } from "@opentelemetry/api"; +import { type MatrixCall } from "matrix-js-sdk/src/matrix"; import { CallEvent } from "matrix-js-sdk/src/webrtc/call"; import { - TransceiverStats, - CallFeedStats, + type TransceiverStats, + type CallFeedStats, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { ObjectFlattener } from "./ObjectFlattener"; import { ElementCallOpenTelemetry } from "./otel"; -import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan"; +import { type OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan"; import { OTelCallTransceiverMediaStreamSpan } from "./OTelCallTransceiverMediaStreamSpan"; import { OTelCallFeedMediaStreamSpan } from "./OTelCallFeedMediaStreamSpan"; diff --git a/src/otel/OTelCallAbstractMediaStreamSpan.ts b/src/otel/OTelCallAbstractMediaStreamSpan.ts index 98862597..59328250 100644 --- a/src/otel/OTelCallAbstractMediaStreamSpan.ts +++ b/src/otel/OTelCallAbstractMediaStreamSpan.ts @@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import opentelemetry, { Span } from "@opentelemetry/api"; -import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport"; +import opentelemetry, { type Span } from "@opentelemetry/api"; +import { type TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport"; -import { ElementCallOpenTelemetry } from "./otel"; +import { type ElementCallOpenTelemetry } from "./otel"; import { OTelCallMediaStreamTrackSpan } from "./OTelCallMediaStreamTrackSpan"; type TrackId = string; diff --git a/src/otel/OTelCallFeedMediaStreamSpan.ts b/src/otel/OTelCallFeedMediaStreamSpan.ts index 68a683fb..5ba9a774 100644 --- a/src/otel/OTelCallFeedMediaStreamSpan.ts +++ b/src/otel/OTelCallFeedMediaStreamSpan.ts @@ -5,13 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { Span } from "@opentelemetry/api"; +import { type Span } from "@opentelemetry/api"; import { - CallFeedStats, - TrackStats, + type CallFeedStats, + type TrackStats, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; -import { ElementCallOpenTelemetry } from "./otel"; +import { type ElementCallOpenTelemetry } from "./otel"; import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan"; export class OTelCallFeedMediaStreamSpan extends OTelCallAbstractMediaStreamSpan { diff --git a/src/otel/OTelCallMediaStreamTrackSpan.ts b/src/otel/OTelCallMediaStreamTrackSpan.ts index cee2b298..50c4c028 100644 --- a/src/otel/OTelCallMediaStreamTrackSpan.ts +++ b/src/otel/OTelCallMediaStreamTrackSpan.ts @@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport"; -import opentelemetry, { Span } from "@opentelemetry/api"; +import { type TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport"; +import opentelemetry, { type Span } from "@opentelemetry/api"; -import { ElementCallOpenTelemetry } from "./otel"; +import { type ElementCallOpenTelemetry } from "./otel"; export class OTelCallMediaStreamTrackSpan { private readonly span: Span; diff --git a/src/otel/OTelCallTransceiverMediaStreamSpan.ts b/src/otel/OTelCallTransceiverMediaStreamSpan.ts index c1fa33a2..a9f780ce 100644 --- a/src/otel/OTelCallTransceiverMediaStreamSpan.ts +++ b/src/otel/OTelCallTransceiverMediaStreamSpan.ts @@ -5,13 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { Span } from "@opentelemetry/api"; +import { type Span } from "@opentelemetry/api"; import { - TrackStats, - TransceiverStats, + type TrackStats, + type TransceiverStats, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; -import { ElementCallOpenTelemetry } from "./otel"; +import { type ElementCallOpenTelemetry } from "./otel"; import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan"; export class OTelCallTransceiverMediaStreamSpan extends OTelCallAbstractMediaStreamSpan { diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index c9eded22..6854a6c4 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -5,31 +5,35 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import opentelemetry, { Span, Attributes, Context } from "@opentelemetry/api"; +import opentelemetry, { + type Span, + type Attributes, + type Context, +} from "@opentelemetry/api"; import { - GroupCall, - MatrixClient, - MatrixEvent, - RoomMember, + type GroupCall, + type MatrixClient, + type MatrixEvent, + type RoomMember, } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { - CallError, - CallState, - MatrixCall, - VoipEvent, + type CallError, + type CallState, + type MatrixCall, + type VoipEvent, } from "matrix-js-sdk/src/webrtc/call"; import { - CallsByUserAndDevice, - GroupCallError, + type CallsByUserAndDevice, + type GroupCallError, GroupCallEvent, - GroupCallStatsReport, + type GroupCallStatsReport, } from "matrix-js-sdk/src/webrtc/groupCall"; import { - ConnectionStatsReport, - ByteSentStatsReport, - SummaryStatsReport, - CallFeedReport, + type ConnectionStatsReport, + type ByteSentStatsReport, + type SummaryStatsReport, + type CallFeedReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { ElementCallOpenTelemetry } from "./otel"; diff --git a/src/otel/ObjectFlattener.test.ts b/src/otel/ObjectFlattener.test.ts index 42f029cb..6a8de58b 100644 --- a/src/otel/ObjectFlattener.test.ts +++ b/src/otel/ObjectFlattener.test.ts @@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; +import { type GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; import { - AudioConcealment, - ByteSentStatsReport, - ConnectionStatsReport, + type AudioConcealment, + type ByteSentStatsReport, + type ConnectionStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { describe, expect, it } from "vitest"; diff --git a/src/otel/ObjectFlattener.ts b/src/otel/ObjectFlattener.ts index ebf66975..622700f2 100644 --- a/src/otel/ObjectFlattener.ts +++ b/src/otel/ObjectFlattener.ts @@ -4,13 +4,13 @@ Copyright 2023, 2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { Attributes } from "@opentelemetry/api"; -import { VoipEvent } from "matrix-js-sdk/src/webrtc/call"; -import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; +import { type Attributes } from "@opentelemetry/api"; +import { type VoipEvent } from "matrix-js-sdk/src/webrtc/call"; +import { type GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; import { - ByteSentStatsReport, - ConnectionStatsReport, - SummaryStatsReport, + type ByteSentStatsReport, + type ConnectionStatsReport, + type SummaryStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; export class ObjectFlattener { diff --git a/src/otel/otel.ts b/src/otel/otel.ts index 14d31ec8..ec982975 100644 --- a/src/otel/otel.ts +++ b/src/otel/otel.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { WebTracerProvider } from "@opentelemetry/sdk-trace-web"; -import opentelemetry, { Tracer } from "@opentelemetry/api"; +import opentelemetry, { type Tracer } from "@opentelemetry/api"; import { Resource } from "@opentelemetry/resources"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/profile/useProfile.ts b/src/profile/useProfile.ts index 6e04eec8..86164104 100644 --- a/src/profile/useProfile.ts +++ b/src/profile/useProfile.ts @@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { MatrixClient } from "matrix-js-sdk/src/client"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { User, UserEvent } from "matrix-js-sdk/src/models/user"; -import { FileType } from "matrix-js-sdk/src/http-api"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; +import { type MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { type User, UserEvent } from "matrix-js-sdk/src/models/user"; +import { type FileType } from "matrix-js-sdk/src/http-api"; import { useState, useCallback, useEffect } from "react"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/reactions/RaisedHandIndicator.tsx b/src/reactions/RaisedHandIndicator.tsx index 8c4747b3..02ca068c 100644 --- a/src/reactions/RaisedHandIndicator.tsx +++ b/src/reactions/RaisedHandIndicator.tsx @@ -6,8 +6,8 @@ Please see LICENSE in the repository root for full details. */ import { - MouseEventHandler, - ReactNode, + type MouseEventHandler, + type ReactNode, useCallback, useEffect, useState, diff --git a/src/reactions/ReactionIndicator.tsx b/src/reactions/ReactionIndicator.tsx index a664df18..e7066e11 100644 --- a/src/reactions/ReactionIndicator.tsx +++ b/src/reactions/ReactionIndicator.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { PropsWithChildren, ReactNode } from "react"; +import { type PropsWithChildren, type ReactNode } from "react"; import classNames from "classnames"; import { useTranslation } from "react-i18next"; diff --git a/src/reactions/index.ts b/src/reactions/index.ts index 610e24f0..f8253c81 100644 --- a/src/reactions/index.ts +++ b/src/reactions/index.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { RelationType } from "matrix-js-sdk/src/types"; +import { type RelationType } from "matrix-js-sdk/src/types"; import catSoundOgg from "../sound/reactions/cat.ogg?url"; import catSoundMp3 from "../sound/reactions/cat.mp3?url"; diff --git a/src/room/AppSelectionModal.tsx b/src/room/AppSelectionModal.tsx index 588fceef..79df17c9 100644 --- a/src/room/AppSelectionModal.tsx +++ b/src/room/AppSelectionModal.tsx @@ -5,7 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, MouseEvent, useCallback, useMemo, useState } from "react"; +import { + type FC, + type MouseEvent, + useCallback, + useMemo, + useState, +} from "react"; import { useTranslation } from "react-i18next"; import { Button, Text } from "@vector-im/compound-web"; import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; diff --git a/src/room/CallEndedView.tsx b/src/room/CallEndedView.tsx index 556dc6e5..3ff4f397 100644 --- a/src/room/CallEndedView.tsx +++ b/src/room/CallEndedView.tsx @@ -5,8 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, FormEventHandler, ReactNode, useCallback, useState } from "react"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { + type FC, + type FormEventHandler, + type ReactNode, + useCallback, + useState, +} from "react"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; import { Trans, useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; import { Button, Heading, Text } from "@vector-im/compound-web"; diff --git a/src/room/CallEventAudioRenderer.test.tsx b/src/room/CallEventAudioRenderer.test.tsx index 5bb1ba19..e8d22704 100644 --- a/src/room/CallEventAudioRenderer.test.tsx +++ b/src/room/CallEventAudioRenderer.test.tsx @@ -10,20 +10,20 @@ import { afterAll, beforeEach, expect, - MockedFunction, + type MockedFunction, test, vitest, } from "vitest"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; import { ConnectionState } from "livekit-client"; import { BehaviorSubject, of } from "rxjs"; import { afterEach } from "node:test"; -import { act, ReactNode } from "react"; +import { act, type ReactNode } from "react"; import { - CallMembership, + type CallMembership, type MatrixRTCSession, } from "matrix-js-sdk/src/matrixrtc"; -import { RoomMember } from "matrix-js-sdk/src/matrix"; +import { type RoomMember } from "matrix-js-sdk/src/matrix"; import { mockLivekitRoom, diff --git a/src/room/CallEventAudioRenderer.tsx b/src/room/CallEventAudioRenderer.tsx index 6f4f0359..3c2e338f 100644 --- a/src/room/CallEventAudioRenderer.tsx +++ b/src/room/CallEventAudioRenderer.tsx @@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ReactNode, useDeferredValue, useEffect, useMemo } from "react"; +import { type ReactNode, useDeferredValue, useEffect, useMemo } from "react"; import { filter, interval, throttle } from "rxjs"; -import { CallViewModel } from "../state/CallViewModel"; +import { type CallViewModel } from "../state/CallViewModel"; import joinCallSoundMp3 from "../sound/join_call.mp3"; import joinCallSoundOgg from "../sound/join_call.ogg"; import leftCallSoundMp3 from "../sound/left_call.mp3"; diff --git a/src/room/EncryptionLock.tsx b/src/room/EncryptionLock.tsx index 74706be1..e93aec98 100644 --- a/src/room/EncryptionLock.tsx +++ b/src/room/EncryptionLock.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC } from "react"; +import { type FC } from "react"; import { Tooltip } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; import { diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 9336ffdd..98bfa1a6 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -5,31 +5,41 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + type FC, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { useHistory } from "react-router-dom"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; import { Room, isE2EESupported as isE2EESupportedBrowser, } from "livekit-client"; import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { JoinRule } from "matrix-js-sdk/src/matrix"; import { Heading, Text } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; import type { IWidgetApiRequest } from "matrix-widget-api"; -import { widget, ElementWidgetActions, JoinCallData } from "../widget"; +import { widget, ElementWidgetActions, type JoinCallData } from "../widget"; import { FullScreenView } from "../FullScreenView"; import { LobbyView } from "./LobbyView"; -import { MatrixInfo } from "./VideoPreview"; +import { type MatrixInfo } from "./VideoPreview"; import { CallEndedView } from "./CallEndedView"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { useProfile } from "../profile/useProfile"; import { findDeviceByName } from "../utils/media"; import { ActiveCall } from "./InCallView"; -import { MUTE_PARTICIPANT_COUNT, MuteStates } from "./MuteStates"; -import { useMediaDevices, MediaDevices } from "../livekit/MediaDevicesContext"; +import { MUTE_PARTICIPANT_COUNT, type MuteStates } from "./MuteStates"; +import { + useMediaDevices, + type MediaDevices, +} from "../livekit/MediaDevicesContext"; import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships"; import { enterRTCSession, leaveRTCSession } from "../rtcSessionHelpers"; import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState"; diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 8710d3e8..6ee9f184 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -10,13 +10,13 @@ import { RoomContext, useLocalParticipant, } from "@livekit/components-react"; -import { ConnectionState, Room } from "livekit-client"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { ConnectionState, type Room } from "livekit-client"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; import { - FC, - PointerEvent, - PropsWithoutRef, - TouchEvent, + type FC, + type PointerEvent, + type PropsWithoutRef, + type TouchEvent, forwardRef, useCallback, useEffect, @@ -25,7 +25,7 @@ import { useState, } from "react"; import useMeasure from "react-use-measure"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import classNames from "classnames"; import { BehaviorSubject, map } from "rxjs"; import { useObservable, useObservableEagerState } from "observable-hooks"; @@ -49,28 +49,32 @@ import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts"; import { ElementWidgetActions, widget } from "../widget"; import styles from "./InCallView.module.css"; import { GridTile } from "../tile/GridTile"; -import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; +import { type OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal"; import { useRageshakeRequestModal } from "../settings/submit-rageshake"; import { RageshakeRequestModal } from "./RageshakeRequestModal"; import { useLiveKit } from "../livekit/useLiveKit"; import { useWakeLock } from "../useWakeLock"; import { useMergedRefs } from "../useMergedRefs"; -import { MuteStates } from "./MuteStates"; -import { MatrixInfo } from "./VideoPreview"; +import { type MuteStates } from "./MuteStates"; +import { type MatrixInfo } from "./VideoPreview"; import { InviteButton } from "../button/InviteButton"; import { LayoutToggle } from "./LayoutToggle"; -import { ECConnectionState } from "../livekit/useECConnectionState"; +import { type ECConnectionState } from "../livekit/useECConnectionState"; import { useOpenIDSFU } from "../livekit/openIDSFU"; -import { CallViewModel, GridMode, Layout } from "../state/CallViewModel"; -import { Grid, TileProps } from "../grid/Grid"; +import { + CallViewModel, + type GridMode, + type Layout, +} from "../state/CallViewModel"; +import { Grid, type TileProps } from "../grid/Grid"; import { useInitial } from "../useInitial"; import { SpotlightTile } from "../tile/SpotlightTile"; -import { EncryptionSystem } from "../e2ee/sharedKeyManagement"; +import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; import { E2eeType } from "../e2ee/e2eeType"; import { makeGridLayout } from "../grid/GridLayout"; import { - CallLayoutOutputs, + type CallLayoutOutputs, defaultPipAlignment, defaultSpotlightAlignment, } from "../grid/CallLayout"; @@ -78,7 +82,7 @@ import { makeOneOnOneLayout } from "../grid/OneOnOneLayout"; import { makeSpotlightExpandedLayout } from "../grid/SpotlightExpandedLayout"; import { makeSpotlightLandscapeLayout } from "../grid/SpotlightLandscapeLayout"; import { makeSpotlightPortraitLayout } from "../grid/SpotlightPortraitLayout"; -import { GridTileViewModel, TileViewModel } from "../state/TileViewModel"; +import { GridTileViewModel, type TileViewModel } from "../state/TileViewModel"; import { ReactionsProvider, useReactions } from "../useReactions"; import { ReactionsAudioRenderer } from "./ReactionAudioRenderer"; import { useSwitchCamera } from "./useSwitchCamera"; diff --git a/src/room/InviteModal.test.tsx b/src/room/InviteModal.test.tsx index 45d903b0..ecd1ee48 100644 --- a/src/room/InviteModal.test.tsx +++ b/src/room/InviteModal.test.tsx @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { render, screen } from "@testing-library/react"; import { expect, test, vi } from "vitest"; -import { Room } from "matrix-js-sdk/src/matrix"; +import { type Room } from "matrix-js-sdk/src/matrix"; import { axe } from "vitest-axe"; import { BrowserRouter } from "react-router-dom"; import userEvent from "@testing-library/user-event"; diff --git a/src/room/InviteModal.tsx b/src/room/InviteModal.tsx index 4ef9a5a5..26bb6bc2 100644 --- a/src/room/InviteModal.tsx +++ b/src/room/InviteModal.tsx @@ -5,9 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, MouseEvent, useCallback, useMemo, useState } from "react"; +import { + type FC, + type MouseEvent, + useCallback, + useMemo, + useState, +} from "react"; import { useTranslation } from "react-i18next"; -import { Room } from "matrix-js-sdk/src/matrix"; +import { type Room } from "matrix-js-sdk/src/matrix"; import { Button, Text } from "@vector-im/compound-web"; import { LinkIcon, diff --git a/src/room/LayoutToggle.tsx b/src/room/LayoutToggle.tsx index 59dff95f..45cecb20 100644 --- a/src/room/LayoutToggle.tsx +++ b/src/room/LayoutToggle.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ChangeEvent, FC, TouchEvent, useCallback } from "react"; +import { type ChangeEvent, type FC, type TouchEvent, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Tooltip } from "@vector-im/compound-web"; import { diff --git a/src/room/LobbyView.tsx b/src/room/LobbyView.tsx index fd3df0c8..e7dfe3c5 100644 --- a/src/room/LobbyView.tsx +++ b/src/room/LobbyView.tsx @@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useCallback, useMemo, useState } from "react"; +import { type FC, useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { Button } from "@vector-im/compound-web"; import classNames from "classnames"; import { useHistory } from "react-router-dom"; import { logger } from "matrix-js-sdk/src/logger"; import { usePreviewTracks } from "@livekit/components-react"; -import { LocalVideoTrack, Track } from "livekit-client"; +import { type LocalVideoTrack, Track } from "livekit-client"; import { useObservable } from "observable-hooks"; import { map } from "rxjs"; @@ -21,8 +21,8 @@ import inCallStyles from "./InCallView.module.css"; import styles from "./LobbyView.module.css"; import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header"; import { useLocationNavigation } from "../useLocationNavigation"; -import { MatrixInfo, VideoPreview } from "./VideoPreview"; -import { MuteStates } from "./MuteStates"; +import { type MatrixInfo, VideoPreview } from "./VideoPreview"; +import { type MuteStates } from "./MuteStates"; import { InviteButton } from "../button/InviteButton"; import { EndCallButton, diff --git a/src/room/MuteStates.test.tsx b/src/room/MuteStates.test.tsx index 6cc5815c..77ab8ace 100644 --- a/src/room/MuteStates.test.tsx +++ b/src/room/MuteStates.test.tsx @@ -6,15 +6,15 @@ Please see LICENSE in the repository root for full details. */ import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; -import React, { ReactNode } from "react"; +import React, { type ReactNode } from "react"; import { beforeEach } from "vitest"; import { render, screen } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import { useMuteStates } from "./MuteStates"; import { - MediaDevice, - MediaDevices, + type MediaDevice, + type MediaDevices, MediaDevicesContext, } from "../livekit/MediaDevicesContext"; import { mockConfig } from "../utils/test"; diff --git a/src/room/MuteStates.ts b/src/room/MuteStates.ts index 1452c250..b4f2b43e 100644 --- a/src/room/MuteStates.ts +++ b/src/room/MuteStates.ts @@ -6,16 +6,19 @@ Please see LICENSE in the repository root for full details. */ import { - Dispatch, - SetStateAction, + type Dispatch, + type SetStateAction, useCallback, useEffect, useMemo, } from "react"; -import { IWidgetApiRequest } from "matrix-widget-api"; +import { type IWidgetApiRequest } from "matrix-widget-api"; import { logger } from "matrix-js-sdk/src/logger"; -import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext"; +import { + type MediaDevice, + useMediaDevices, +} from "../livekit/MediaDevicesContext"; import { useReactiveState } from "../useReactiveState"; import { ElementWidgetActions, widget } from "../widget"; import { Config } from "../config/Config"; diff --git a/src/room/RageshakeRequestModal.tsx b/src/room/RageshakeRequestModal.tsx index d22b0bea..d240cb73 100644 --- a/src/room/RageshakeRequestModal.tsx +++ b/src/room/RageshakeRequestModal.tsx @@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useEffect } from "react"; +import { type FC, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Button, Text } from "@vector-im/compound-web"; -import { Modal, Props as ModalProps } from "../Modal"; +import { Modal, type Props as ModalProps } from "../Modal"; import { FieldRow, ErrorMessage } from "../input/Input"; import { useSubmitRageshake } from "../settings/submit-rageshake"; diff --git a/src/room/ReactionAudioRenderer.test.tsx b/src/room/ReactionAudioRenderer.test.tsx index 2fec8a9a..0ab283a9 100644 --- a/src/room/ReactionAudioRenderer.test.tsx +++ b/src/room/ReactionAudioRenderer.test.tsx @@ -12,11 +12,11 @@ import { expect, test, vitest, - MockedFunction, - Mock, + type MockedFunction, + type Mock, } from "vitest"; import { TooltipProvider } from "@vector-im/compound-web"; -import { act, ReactNode } from "react"; +import { act, type ReactNode } from "react"; import { afterEach } from "node:test"; import { diff --git a/src/room/ReactionAudioRenderer.tsx b/src/room/ReactionAudioRenderer.tsx index 15bfc90f..6463c9d1 100644 --- a/src/room/ReactionAudioRenderer.tsx +++ b/src/room/ReactionAudioRenderer.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ReactNode, useDeferredValue, useEffect, useState } from "react"; +import { type ReactNode, useDeferredValue, useEffect, useState } from "react"; import { useReactions } from "../useReactions"; import { playReactionsSound, useSetting } from "../settings/settings"; diff --git a/src/room/ReactionsOverlay.test.tsx b/src/room/ReactionsOverlay.test.tsx index 121594ab..8ea17178 100644 --- a/src/room/ReactionsOverlay.test.tsx +++ b/src/room/ReactionsOverlay.test.tsx @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { render } from "@testing-library/react"; import { expect, test } from "vitest"; import { TooltipProvider } from "@vector-im/compound-web"; -import { act, ReactNode } from "react"; +import { act, type ReactNode } from "react"; import { afterEach } from "node:test"; import { diff --git a/src/room/ReactionsOverlay.tsx b/src/room/ReactionsOverlay.tsx index 7cdf7568..2f8daba5 100644 --- a/src/room/ReactionsOverlay.tsx +++ b/src/room/ReactionsOverlay.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ReactNode, useMemo } from "react"; +import { type ReactNode, useMemo } from "react"; import { useReactions } from "../useReactions"; import { diff --git a/src/room/RoomAuthView.tsx b/src/room/RoomAuthView.tsx index 2c7fd489..33aeb4c8 100644 --- a/src/room/RoomAuthView.tsx +++ b/src/room/RoomAuthView.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useCallback, useState } from "react"; +import { type FC, useCallback, useState } from "react"; import { useLocation } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx index 49d594bb..8c88b985 100644 --- a/src/room/RoomPage.tsx +++ b/src/room/RoomPage.tsx @@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useEffect, useState, ReactNode, useRef } from "react"; +import { type FC, useEffect, useState, type ReactNode, useRef } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { useTranslation } from "react-i18next"; import { CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; -import { MatrixError } from "matrix-js-sdk/src/http-api"; +import { type MatrixError } from "matrix-js-sdk/src/http-api"; import { Heading, Text } from "@vector-im/compound-web"; import { useClientLegacy } from "../ClientContext"; diff --git a/src/room/VideoPreview.tsx b/src/room/VideoPreview.tsx index af05369e..f9609b99 100644 --- a/src/room/VideoPreview.tsx +++ b/src/room/VideoPreview.tsx @@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { useEffect, useRef, FC, ReactNode } from "react"; +import { useEffect, useRef, type FC, type ReactNode } from "react"; import useMeasure from "react-use-measure"; -import { facingModeFromLocalTrack, LocalVideoTrack } from "livekit-client"; +import { facingModeFromLocalTrack, type LocalVideoTrack } from "livekit-client"; import classNames from "classnames"; import { Avatar } from "../Avatar"; import styles from "./VideoPreview.module.css"; -import { MuteStates } from "./MuteStates"; -import { EncryptionSystem } from "../e2ee/sharedKeyManagement"; +import { type MuteStates } from "./MuteStates"; +import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; export type MatrixInfo = { userId: string; diff --git a/src/room/checkForParallelCalls.test.ts b/src/room/checkForParallelCalls.test.ts index 59378e43..c434620d 100644 --- a/src/room/checkForParallelCalls.test.ts +++ b/src/room/checkForParallelCalls.test.ts @@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { vi, Mocked, test, expect } from "vitest"; -import { RoomState } from "matrix-js-sdk/src/models/room-state"; +import { vi, type Mocked, test, expect } from "vitest"; +import { type RoomState } from "matrix-js-sdk/src/models/room-state"; import { PosthogAnalytics } from "../../src/analytics/PosthogAnalytics"; import { checkForParallelCalls } from "../../src/room/checkForParallelCalls"; diff --git a/src/room/checkForParallelCalls.ts b/src/room/checkForParallelCalls.ts index 92fa8174..f1687604 100644 --- a/src/room/checkForParallelCalls.ts +++ b/src/room/checkForParallelCalls.ts @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { EventType } from "matrix-js-sdk/src/@types/event"; -import { RoomState } from "matrix-js-sdk/src/models/room-state"; +import { type RoomState } from "matrix-js-sdk/src/models/room-state"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; diff --git a/src/room/useActiveFocus.ts b/src/room/useActiveFocus.ts index bdfe0d02..79b5b6cb 100644 --- a/src/room/useActiveFocus.ts +++ b/src/room/useActiveFocus.ts @@ -6,14 +6,14 @@ Please see LICENSE in the repository root for full details. */ import { - MatrixRTCSession, + type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { useCallback, useEffect, useState } from "react"; import { deepCompare } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; import { - LivekitFocus, + type LivekitFocus, isLivekitFocus, } from "matrix-js-sdk/src/matrixrtc/LivekitFocus"; diff --git a/src/room/useJoinRule.ts b/src/room/useJoinRule.ts index 7900a174..6939a952 100644 --- a/src/room/useJoinRule.ts +++ b/src/room/useJoinRule.ts @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { useCallback } from "react"; -import { JoinRule } from "matrix-js-sdk/src/matrix"; +import { type JoinRule } from "matrix-js-sdk/src/matrix"; import type { Room } from "matrix-js-sdk/src/models/room"; import { useRoomState } from "./useRoomState"; diff --git a/src/room/useLoadGroupCall.ts b/src/room/useLoadGroupCall.ts index 163571c8..5edf49d7 100644 --- a/src/room/useLoadGroupCall.ts +++ b/src/room/useLoadGroupCall.ts @@ -10,12 +10,12 @@ import { logger } from "matrix-js-sdk/src/logger"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { ClientEvent, - MatrixClient, - RoomSummary, + type MatrixClient, + type RoomSummary, } from "matrix-js-sdk/src/client"; import { SyncState } from "matrix-js-sdk/src/sync"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; -import { RoomEvent, Room } from "matrix-js-sdk/src/models/room"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { RoomEvent, type Room } from "matrix-js-sdk/src/models/room"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { JoinRule, MatrixError } from "matrix-js-sdk/src/matrix"; import { useTranslation } from "react-i18next"; diff --git a/src/room/useRoomAvatar.ts b/src/room/useRoomAvatar.ts index 377dd7fd..990880eb 100644 --- a/src/room/useRoomAvatar.ts +++ b/src/room/useRoomAvatar.ts @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { useCallback } from "react"; -import { Room } from "matrix-js-sdk/src/models/room"; +import { type Room } from "matrix-js-sdk/src/models/room"; import { useRoomState } from "./useRoomState"; diff --git a/src/room/useRoomName.ts b/src/room/useRoomName.ts index b58d3be1..f49a84b2 100644 --- a/src/room/useRoomName.ts +++ b/src/room/useRoomName.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { Room, RoomEvent } from "matrix-js-sdk/src/matrix"; +import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix"; import { useState } from "react"; import { useTypedEventEmitter } from "../useEvents"; diff --git a/src/room/useRoomState.ts b/src/room/useRoomState.ts index f4d52ff5..d7cc9b6b 100644 --- a/src/room/useRoomState.ts +++ b/src/room/useRoomState.ts @@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { RoomState, RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; +import { + type RoomState, + RoomStateEvent, +} from "matrix-js-sdk/src/models/room-state"; import { useCallback, useMemo, useState } from "react"; import type { Room } from "matrix-js-sdk/src/models/room"; diff --git a/src/room/useSwitchCamera.ts b/src/room/useSwitchCamera.ts index c1b4f092..8bbfc92c 100644 --- a/src/room/useSwitchCamera.ts +++ b/src/room/useSwitchCamera.ts @@ -9,14 +9,14 @@ import { fromEvent, map, merge, - Observable, + type Observable, of, startWith, switchMap, } from "rxjs"; import { facingModeFromLocalTrack, - LocalVideoTrack, + type LocalVideoTrack, TrackEvent, } from "livekit-client"; import { useObservable, useObservableEagerState } from "observable-hooks"; diff --git a/src/rtcSessionHelper.test.ts b/src/rtcSessionHelper.test.ts index 61cd990e..7df9f1b3 100644 --- a/src/rtcSessionHelper.test.ts +++ b/src/rtcSessionHelper.test.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { expect, test, vi } from "vitest"; import { enterRTCSession } from "../src/rtcSessionHelpers"; diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index 9e699319..015bf627 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -5,18 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { logger } from "matrix-js-sdk/src/logger"; import { - LivekitFocus, - LivekitFocusActive, + type LivekitFocus, + type LivekitFocusActive, isLivekitFocus, isLivekitFocusConfig, } from "matrix-js-sdk/src/matrixrtc/LivekitFocus"; import { PosthogAnalytics } from "./analytics/PosthogAnalytics"; import { Config } from "./config/Config"; -import { ElementWidgetActions, WidgetHelpers, widget } from "./widget"; +import { ElementWidgetActions, type WidgetHelpers, widget } from "./widget"; const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; diff --git a/src/settings/DeviceSelection.tsx b/src/settings/DeviceSelection.tsx index 005973a0..da4fe11a 100644 --- a/src/settings/DeviceSelection.tsx +++ b/src/settings/DeviceSelection.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ChangeEvent, FC, useCallback, useId } from "react"; +import { type ChangeEvent, type FC, useCallback, useId } from "react"; import { Heading, InlineField, @@ -14,7 +14,7 @@ import { Separator, } from "@vector-im/compound-web"; -import { MediaDevice } from "../livekit/MediaDevicesContext"; +import { type MediaDevice } from "../livekit/MediaDevicesContext"; import styles from "./DeviceSelection.module.css"; interface Props { diff --git a/src/settings/FeedbackSettingsTab.tsx b/src/settings/FeedbackSettingsTab.tsx index 455995a1..f77da898 100644 --- a/src/settings/FeedbackSettingsTab.tsx +++ b/src/settings/FeedbackSettingsTab.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useCallback } from "react"; +import { type FC, useCallback } from "react"; import { randomString } from "matrix-js-sdk/src/randomstring"; import { useTranslation } from "react-i18next"; import { Button, Text } from "@vector-im/compound-web"; diff --git a/src/settings/PreferencesSettingsTab.tsx b/src/settings/PreferencesSettingsTab.tsx index bc98d181..32200f7b 100644 --- a/src/settings/PreferencesSettingsTab.tsx +++ b/src/settings/PreferencesSettingsTab.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ChangeEvent, FC } from "react"; +import { type ChangeEvent, type FC } from "react"; import { useTranslation } from "react-i18next"; import { Text } from "@vector-im/compound-web"; diff --git a/src/settings/ProfileSettingsTab.tsx b/src/settings/ProfileSettingsTab.tsx index 4eb5b0d9..94d43f04 100644 --- a/src/settings/ProfileSettingsTab.tsx +++ b/src/settings/ProfileSettingsTab.tsx @@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { FC, useCallback, useEffect, useMemo, useRef } from "react"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { type FC, useCallback, useEffect, useMemo, useRef } from "react"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; import { useTranslation } from "react-i18next"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/settings/RageshakeButton.tsx b/src/settings/RageshakeButton.tsx index 0854da6e..fa17b788 100644 --- a/src/settings/RageshakeButton.tsx +++ b/src/settings/RageshakeButton.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { useTranslation } from "react-i18next"; -import { FC, useCallback } from "react"; +import { type FC, useCallback } from "react"; import { Button } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 9d7eb5a1..6bb8d42b 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -5,14 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ChangeEvent, FC, useCallback, useState } from "react"; +import { type ChangeEvent, type FC, useCallback, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { Root as Form, Text } from "@vector-im/compound-web"; import { Modal } from "../Modal"; import styles from "./SettingsModal.module.css"; -import { Tab, TabContainer } from "../tabs/Tabs"; +import { type Tab, TabContainer } from "../tabs/Tabs"; import { FieldRow, InputField } from "../input/Input"; import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; import { ProfileSettingsTab } from "./ProfileSettingsTab"; diff --git a/src/settings/rageshake.ts b/src/settings/rageshake.ts index 1030042f..c2d0ebf8 100644 --- a/src/settings/rageshake.ts +++ b/src/settings/rageshake.ts @@ -29,9 +29,11 @@ Please see LICENSE in the repository root for full details. import EventEmitter from "events"; import { throttle } from "lodash-es"; -import { Logger, logger } from "matrix-js-sdk/src/logger"; +import { type Logger, logger } from "matrix-js-sdk/src/logger"; import { randomString } from "matrix-js-sdk/src/randomstring"; -import loglevel, { LoggingMethod } from "loglevel"; +import { type LoggingMethod } from "loglevel"; + +import type loglevel from "loglevel"; // the length of log data we keep in indexeddb (and include in the reports) const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB diff --git a/src/settings/settings.ts b/src/settings/settings.ts index b2da1674..8d32adbe 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { logger } from "matrix-js-sdk/src/logger"; -import { BehaviorSubject, Observable } from "rxjs"; +import { BehaviorSubject, type Observable } from "rxjs"; import { useObservableEagerState } from "observable-hooks"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; diff --git a/src/settings/submit-rageshake.ts b/src/settings/submit-rageshake.ts index ae320493..9a3529d5 100644 --- a/src/settings/submit-rageshake.ts +++ b/src/settings/submit-rageshake.ts @@ -5,20 +5,20 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ComponentProps, useCallback, useEffect, useState } from "react"; +import { type ComponentProps, useCallback, useEffect, useState } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { ClientEvent, - Crypto, - MatrixClient, - MatrixEvent, + type Crypto, + type MatrixClient, + type MatrixEvent, } from "matrix-js-sdk/src/matrix"; import { getLogsForReport } from "./rageshake"; import { useClient } from "../ClientContext"; import { Config } from "../config/Config"; import { ElementCallOpenTelemetry } from "../otel/otel"; -import { RageshakeRequestModal } from "../room/RageshakeRequestModal"; +import { type RageshakeRequestModal } from "../room/RageshakeRequestModal"; const gzip = async (text: string): Promise => { // pako is relatively large (200KB), so we only import it when needed diff --git a/src/state/CallViewModel.test.ts b/src/state/CallViewModel.test.ts index 5dbfb1ca..ad7e8702 100644 --- a/src/state/CallViewModel.test.ts +++ b/src/state/CallViewModel.test.ts @@ -11,23 +11,26 @@ import { debounceTime, distinctUntilChanged, map, - Observable, + type Observable, of, switchMap, } from "rxjs"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { ConnectionState, - LocalParticipant, - Participant, + type LocalParticipant, + type Participant, ParticipantEvent, - RemoteParticipant, + type RemoteParticipant, } from "livekit-client"; import * as ComponentsCore from "@livekit/components-core"; import { isEqual } from "lodash-es"; -import { CallMembership, MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; +import { + type CallMembership, + type MatrixRTCSession, +} from "matrix-js-sdk/src/matrixrtc"; -import { CallViewModel, Layout } from "./CallViewModel"; +import { CallViewModel, type Layout } from "./CallViewModel"; import { mockLivekitRoom, mockLocalParticipant, @@ -40,7 +43,7 @@ import { } from "../utils/test"; import { ECAddonConnectionState, - ECConnectionState, + type ECConnectionState, } from "../livekit/useECConnectionState"; import { E2eeType } from "../e2ee/e2eeType"; diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index af8780b1..a2a10884 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -11,18 +11,21 @@ import { observeParticipantMedia, } from "@livekit/components-core"; import { - Room as LivekitRoom, - LocalParticipant, + type Room as LivekitRoom, + type LocalParticipant, LocalVideoTrack, ParticipantEvent, - RemoteParticipant, + type RemoteParticipant, Track, } from "livekit-client"; -import { Room as MatrixRoom, RoomMember } from "matrix-js-sdk/src/matrix"; +import { + type Room as MatrixRoom, + type RoomMember, +} from "matrix-js-sdk/src/matrix"; import { BehaviorSubject, EMPTY, - Observable, + type Observable, Subject, combineLatest, concat, @@ -47,35 +50,38 @@ import { } from "rxjs"; import { logger } from "matrix-js-sdk/src/logger"; import { - MatrixRTCSession, + type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/src/matrixrtc"; import { ViewModel } from "./ViewModel"; import { ECAddonConnectionState, - ECConnectionState, + type ECConnectionState, } from "../livekit/useECConnectionState"; import { LocalUserMediaViewModel, - MediaViewModel, + type MediaViewModel, observeTrackReference, RemoteUserMediaViewModel, ScreenShareViewModel, - UserMediaViewModel, + type UserMediaViewModel, } from "./MediaViewModel"; import { accumulate, finalizeValue } from "../utils/observable"; import { ObservableScope } from "./ObservableScope"; import { duplicateTiles } from "../settings/settings"; import { isFirefox } from "../Platform"; import { setPipEnabled } from "../controls"; -import { GridTileViewModel, SpotlightTileViewModel } from "./TileViewModel"; +import { + type GridTileViewModel, + type SpotlightTileViewModel, +} from "./TileViewModel"; import { TileStore } from "./TileStore"; import { gridLikeLayout } from "./GridLikeLayout"; import { spotlightExpandedLayout } from "./SpotlightExpandedLayout"; import { oneOnOneLayout } from "./OneOnOneLayout"; import { pipLayout } from "./PipLayout"; -import { EncryptionSystem } from "../e2ee/sharedKeyManagement"; +import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; import { observeSpeaker } from "./observeSpeaker"; // How long we wait after a focus switch before showing the real participant diff --git a/src/state/GridLikeLayout.ts b/src/state/GridLikeLayout.ts index 7fcada95..b846939e 100644 --- a/src/state/GridLikeLayout.ts +++ b/src/state/GridLikeLayout.ts @@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { Layout, LayoutMedia } from "./CallViewModel"; -import { TileStore } from "./TileStore"; -import { GridTileViewModel } from "./TileViewModel"; +import { type Layout, type LayoutMedia } from "./CallViewModel"; +import { type TileStore } from "./TileStore"; +import { type GridTileViewModel } from "./TileViewModel"; export type GridLikeLayoutType = | "grid" diff --git a/src/state/MediaViewModel.ts b/src/state/MediaViewModel.ts index ceaca57c..ea015eb8 100644 --- a/src/state/MediaViewModel.ts +++ b/src/state/MediaViewModel.ts @@ -6,30 +6,30 @@ Please see LICENSE in the repository root for full details. */ import { - AudioSource, - TrackReferenceOrPlaceholder, - VideoSource, + type AudioSource, + type TrackReferenceOrPlaceholder, + type VideoSource, observeParticipantEvents, observeParticipantMedia, roomEventSelector, } from "@livekit/components-core"; import { - LocalParticipant, + type LocalParticipant, LocalTrack, - Participant, + type Participant, ParticipantEvent, - RemoteParticipant, + type RemoteParticipant, Track, TrackEvent, facingModeFromLocalTrack, - Room as LivekitRoom, + type Room as LivekitRoom, RoomEvent as LivekitRoomEvent, RemoteTrack, } from "livekit-client"; -import { RoomMember, RoomMemberEvent } from "matrix-js-sdk/src/matrix"; +import { type RoomMember, RoomMemberEvent } from "matrix-js-sdk/src/matrix"; import { BehaviorSubject, - Observable, + type Observable, Subject, combineLatest, distinctUntilKeyChanged, @@ -49,7 +49,7 @@ import { ViewModel } from "./ViewModel"; import { useReactiveState } from "../useReactiveState"; import { alwaysShowSelf } from "../settings/settings"; import { accumulate } from "../utils/observable"; -import { EncryptionSystem } from "../e2ee/sharedKeyManagement"; +import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; import { E2eeType } from "../e2ee/e2eeType"; // TODO: Move this naming logic into the view model diff --git a/src/state/ObservableScope.ts b/src/state/ObservableScope.ts index 2cf88351..5a2e0e9a 100644 --- a/src/state/ObservableScope.ts +++ b/src/state/ObservableScope.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { distinctUntilChanged, - Observable, + type Observable, shareReplay, Subject, takeUntil, diff --git a/src/state/OneOnOneLayout.ts b/src/state/OneOnOneLayout.ts index 29ed9fc0..f078b7e3 100644 --- a/src/state/OneOnOneLayout.ts +++ b/src/state/OneOnOneLayout.ts @@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { OneOnOneLayout, OneOnOneLayoutMedia } from "./CallViewModel"; -import { TileStore } from "./TileStore"; -import { GridTileViewModel } from "./TileViewModel"; +import { type OneOnOneLayout, type OneOnOneLayoutMedia } from "./CallViewModel"; +import { type TileStore } from "./TileStore"; +import { type GridTileViewModel } from "./TileViewModel"; /** * Produces a one-on-one layout with the given media. diff --git a/src/state/PipLayout.ts b/src/state/PipLayout.ts index 35edeefe..79187752 100644 --- a/src/state/PipLayout.ts +++ b/src/state/PipLayout.ts @@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { PipLayout, PipLayoutMedia } from "./CallViewModel"; -import { TileStore } from "./TileStore"; -import { GridTileViewModel } from "./TileViewModel"; +import { type PipLayout, type PipLayoutMedia } from "./CallViewModel"; +import { type TileStore } from "./TileStore"; +import { type GridTileViewModel } from "./TileViewModel"; /** * Produces a picture-in-picture layout with the given media. diff --git a/src/state/SpotlightExpandedLayout.ts b/src/state/SpotlightExpandedLayout.ts index 83c5a95e..56b1d01d 100644 --- a/src/state/SpotlightExpandedLayout.ts +++ b/src/state/SpotlightExpandedLayout.ts @@ -6,11 +6,11 @@ Please see LICENSE in the repository root for full details. */ import { - SpotlightExpandedLayout, - SpotlightExpandedLayoutMedia, + type SpotlightExpandedLayout, + type SpotlightExpandedLayoutMedia, } from "./CallViewModel"; -import { TileStore } from "./TileStore"; -import { GridTileViewModel } from "./TileViewModel"; +import { type TileStore } from "./TileStore"; +import { type GridTileViewModel } from "./TileViewModel"; /** * Produces an expanded spotlight layout with the given media. diff --git a/src/state/TileStore.ts b/src/state/TileStore.ts index 0288830c..ab918cb0 100644 --- a/src/state/TileStore.ts +++ b/src/state/TileStore.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { BehaviorSubject } from "rxjs"; -import { MediaViewModel, UserMediaViewModel } from "./MediaViewModel"; +import { type MediaViewModel, type UserMediaViewModel } from "./MediaViewModel"; import { GridTileViewModel, SpotlightTileViewModel } from "./TileViewModel"; import { fillGaps } from "../utils/iter"; diff --git a/src/state/TileViewModel.ts b/src/state/TileViewModel.ts index 3c25907e..53bc8648 100644 --- a/src/state/TileViewModel.ts +++ b/src/state/TileViewModel.ts @@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { BehaviorSubject, Observable } from "rxjs"; +import { BehaviorSubject, type Observable } from "rxjs"; import { ViewModel } from "./ViewModel"; -import { MediaViewModel, UserMediaViewModel } from "./MediaViewModel"; +import { type MediaViewModel, type UserMediaViewModel } from "./MediaViewModel"; let nextId = 0; function createId(): string { diff --git a/src/state/observeSpeaker.ts b/src/state/observeSpeaker.ts index d32fbdaa..cce43ef9 100644 --- a/src/state/observeSpeaker.ts +++ b/src/state/observeSpeaker.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ import { - Observable, + type Observable, audit, merge, timer, diff --git a/src/tabs/Tabs.tsx b/src/tabs/Tabs.tsx index 8f063a97..287be30d 100644 --- a/src/tabs/Tabs.tsx +++ b/src/tabs/Tabs.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { Key, ReactNode, useId } from "react"; +import { type Key, type ReactNode, useId } from "react"; import { NavBar, NavItem } from "@vector-im/compound-web"; import styles from "./Tabs.module.css"; diff --git a/src/tile/GridTile.test.tsx b/src/tile/GridTile.test.tsx index c0cf9c48..d7edf3b3 100644 --- a/src/tile/GridTile.test.tsx +++ b/src/tile/GridTile.test.tsx @@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { RemoteTrackPublication } from "livekit-client"; +import { type RemoteTrackPublication } from "livekit-client"; import { test, expect } from "vitest"; import { render, screen } from "@testing-library/react"; import { axe } from "vitest-axe"; import { of } from "rxjs"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { GridTile } from "./GridTile"; import { mockRtcMembership, withRemoteMedia } from "../utils/test"; diff --git a/src/tile/GridTile.tsx b/src/tile/GridTile.tsx index 15f7c295..73c17527 100644 --- a/src/tile/GridTile.tsx +++ b/src/tile/GridTile.tsx @@ -6,14 +6,14 @@ Please see LICENSE in the repository root for full details. */ import { - ComponentProps, - ReactNode, + type ComponentProps, + type ReactNode, forwardRef, useCallback, useRef, useState, } from "react"; -import { animated } from "@react-spring/web"; +import { type animated } from "@react-spring/web"; import classNames from "classnames"; import { useTranslation } from "react-i18next"; import { @@ -38,18 +38,18 @@ import { useObservableEagerState } from "observable-hooks"; import styles from "./GridTile.module.css"; import { - UserMediaViewModel, + type UserMediaViewModel, useDisplayName, LocalUserMediaViewModel, - RemoteUserMediaViewModel, + type RemoteUserMediaViewModel, } from "../state/MediaViewModel"; import { Slider } from "../Slider"; import { MediaView } from "./MediaView"; import { useLatest } from "../useLatest"; -import { GridTileViewModel } from "../state/TileViewModel"; +import { type GridTileViewModel } from "../state/TileViewModel"; import { useMergedRefs } from "../useMergedRefs"; import { useReactions } from "../useReactions"; -import { ReactionOption } from "../reactions"; +import { type ReactionOption } from "../reactions"; interface TileProps { className?: string; diff --git a/src/tile/MediaView.test.tsx b/src/tile/MediaView.test.tsx index fea4303f..55ad8a12 100644 --- a/src/tile/MediaView.test.tsx +++ b/src/tile/MediaView.test.tsx @@ -10,8 +10,8 @@ import { render, screen } from "@testing-library/react"; import { axe } from "vitest-axe"; import { TooltipProvider } from "@vector-im/compound-web"; import { - TrackReference, - TrackReferencePlaceholder, + type TrackReference, + type TrackReferencePlaceholder, } from "@livekit/components-core"; import { Track, TrackPublication } from "livekit-client"; import { type ComponentProps } from "react"; diff --git a/src/tile/MediaView.tsx b/src/tile/MediaView.tsx index 48871abd..0d5341a8 100644 --- a/src/tile/MediaView.tsx +++ b/src/tile/MediaView.tsx @@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { TrackReferenceOrPlaceholder } from "@livekit/components-core"; +import { type TrackReferenceOrPlaceholder } from "@livekit/components-core"; import { animated } from "@react-spring/web"; -import { RoomMember } from "matrix-js-sdk/src/matrix"; -import { ComponentProps, ReactNode, forwardRef } from "react"; +import { type RoomMember } from "matrix-js-sdk/src/matrix"; +import { type ComponentProps, type ReactNode, forwardRef } from "react"; import { useTranslation } from "react-i18next"; import classNames from "classnames"; import { VideoTrack } from "@livekit/components-react"; @@ -17,10 +17,10 @@ import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import styles from "./MediaView.module.css"; import { Avatar } from "../Avatar"; -import { EncryptionStatus } from "../state/MediaViewModel"; +import { type EncryptionStatus } from "../state/MediaViewModel"; import { RaisedHandIndicator } from "../reactions/RaisedHandIndicator"; import { showHandRaisedTimer, useSetting } from "../settings/settings"; -import { ReactionOption } from "../reactions"; +import { type ReactionOption } from "../reactions"; import { ReactionIndicator } from "../reactions/ReactionIndicator"; interface Props extends ComponentProps { diff --git a/src/tile/SpotlightTile.tsx b/src/tile/SpotlightTile.tsx index dce30d5f..a1c3d46f 100644 --- a/src/tile/SpotlightTile.tsx +++ b/src/tile/SpotlightTile.tsx @@ -6,8 +6,8 @@ Please see LICENSE in the repository root for full details. */ import { - ComponentProps, - RefAttributes, + type ComponentProps, + type RefAttributes, forwardRef, useCallback, useEffect, @@ -21,28 +21,28 @@ import { ChevronRightIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { animated } from "@react-spring/web"; -import { Observable, map } from "rxjs"; +import { type Observable, map } from "rxjs"; import { useObservableEagerState, useObservableRef } from "observable-hooks"; import { useTranslation } from "react-i18next"; import classNames from "classnames"; -import { TrackReferenceOrPlaceholder } from "@livekit/components-core"; -import { RoomMember } from "matrix-js-sdk/src/matrix"; +import { type TrackReferenceOrPlaceholder } from "@livekit/components-core"; +import { type RoomMember } from "matrix-js-sdk/src/matrix"; import { MediaView } from "./MediaView"; import styles from "./SpotlightTile.module.css"; import { - EncryptionStatus, + type EncryptionStatus, LocalUserMediaViewModel, - MediaViewModel, + type MediaViewModel, ScreenShareViewModel, - UserMediaViewModel, + type UserMediaViewModel, useDisplayName, } from "../state/MediaViewModel"; import { useInitial } from "../useInitial"; import { useMergedRefs } from "../useMergedRefs"; import { useReactiveState } from "../useReactiveState"; import { useLatest } from "../useLatest"; -import { SpotlightTileViewModel } from "../state/TileViewModel"; +import { type SpotlightTileViewModel } from "../state/TileViewModel"; interface SpotlightItemBaseProps { className?: string; diff --git a/src/useAudioContext.test.tsx b/src/useAudioContext.test.tsx index 5a1afe43..2b9b0982 100644 --- a/src/useAudioContext.test.tsx +++ b/src/useAudioContext.test.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { expect, test, vitest } from "vitest"; -import { FC } from "react"; +import { type FC } from "react"; import { render } from "@testing-library/react"; import { afterEach } from "node:test"; import userEvent from "@testing-library/user-event"; diff --git a/src/useAudioContext.tsx b/src/useAudioContext.tsx index ccf4cbd5..1580f32a 100644 --- a/src/useAudioContext.tsx +++ b/src/useAudioContext.tsx @@ -13,7 +13,7 @@ import { useSetting, } from "./settings/settings"; import { useMediaDevices } from "./livekit/MediaDevicesContext"; -import { PrefetchedSounds } from "./soundUtils"; +import { type PrefetchedSounds } from "./soundUtils"; /** * Play a sound though a given AudioContext. Will take diff --git a/src/useCallViewKeyboardShortcuts.test.tsx b/src/useCallViewKeyboardShortcuts.test.tsx index fdf7ed85..8c25bb57 100644 --- a/src/useCallViewKeyboardShortcuts.test.tsx +++ b/src/useCallViewKeyboardShortcuts.test.tsx @@ -6,13 +6,17 @@ Please see LICENSE in the repository root for full details. */ import { render } from "@testing-library/react"; -import { FC, useRef } from "react"; +import { type FC, useRef } from "react"; import { expect, test, vi } from "vitest"; import { Button } from "@vector-im/compound-web"; import userEvent from "@testing-library/user-event"; import { useCallViewKeyboardShortcuts } from "../src/useCallViewKeyboardShortcuts"; -import { ReactionOption, ReactionSet, ReactionsRowSize } from "./reactions"; +import { + type ReactionOption, + ReactionSet, + ReactionsRowSize, +} from "./reactions"; // Test Explanation: // - The main objective is to test `useCallViewKeyboardShortcuts`. diff --git a/src/useCallViewKeyboardShortcuts.ts b/src/useCallViewKeyboardShortcuts.ts index 77028a27..a426be58 100644 --- a/src/useCallViewKeyboardShortcuts.ts +++ b/src/useCallViewKeyboardShortcuts.ts @@ -5,10 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { RefObject, useCallback, useMemo, useRef } from "react"; +import { type RefObject, useCallback, useMemo, useRef } from "react"; import { useEventTarget } from "./useEvents"; -import { ReactionOption, ReactionSet, ReactionsRowSize } from "./reactions"; +import { + type ReactionOption, + ReactionSet, + ReactionsRowSize, +} from "./reactions"; /** * Determines whether focus is in the same part of the tree as the given diff --git a/src/useLatest.ts b/src/useLatest.ts index 6a7ec41a..c54eb6c4 100644 --- a/src/useLatest.ts +++ b/src/useLatest.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { RefObject, useRef } from "react"; +import { type RefObject, useRef } from "react"; export interface LatestRef extends RefObject { current: T; diff --git a/src/useMatrixRTCSessionJoinState.ts b/src/useMatrixRTCSessionJoinState.ts index 42fed070..0bdaa25d 100644 --- a/src/useMatrixRTCSessionJoinState.ts +++ b/src/useMatrixRTCSessionJoinState.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { logger } from "matrix-js-sdk/src/logger"; import { - MatrixRTCSession, + type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { useCallback, useEffect, useState } from "react"; diff --git a/src/useMatrixRTCSessionMemberships.ts b/src/useMatrixRTCSessionMemberships.ts index 942a2257..fa9e8f46 100644 --- a/src/useMatrixRTCSessionMemberships.ts +++ b/src/useMatrixRTCSessionMemberships.ts @@ -6,9 +6,9 @@ Please see LICENSE in the repository root for full details. */ import { logger } from "matrix-js-sdk/src/logger"; -import { CallMembership } from "matrix-js-sdk/src/matrixrtc/CallMembership"; +import { type CallMembership } from "matrix-js-sdk/src/matrixrtc/CallMembership"; import { - MatrixRTCSession, + type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { useCallback, useEffect, useState } from "react"; diff --git a/src/useMergedRefs.ts b/src/useMergedRefs.ts index 20627b30..03093b77 100644 --- a/src/useMergedRefs.ts +++ b/src/useMergedRefs.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { MutableRefObject, RefCallback, useCallback } from "react"; +import { type MutableRefObject, type RefCallback, useCallback } from "react"; /** * Combines multiple refs into one, useful for attaching multiple refs to the diff --git a/src/useReactions.test.tsx b/src/useReactions.test.tsx index 6140793f..56edd7e1 100644 --- a/src/useReactions.test.tsx +++ b/src/useReactions.test.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { render } from "@testing-library/react"; -import { act, FC } from "react"; +import { act, type FC } from "react"; import { describe, expect, test } from "vitest"; import { RoomEvent } from "matrix-js-sdk/src/matrix"; diff --git a/src/useReactions.tsx b/src/useReactions.tsx index 7195cfd0..7289187a 100644 --- a/src/useReactions.tsx +++ b/src/useReactions.tsx @@ -7,31 +7,31 @@ Please see LICENSE in the repository root for full details. import { EventType, - MatrixEvent, + type MatrixEvent, RelationType, RoomEvent as MatrixRoomEvent, MatrixEventEvent, } from "matrix-js-sdk/src/matrix"; -import { ReactionEventContent } from "matrix-js-sdk/src/types"; +import { type ReactionEventContent } from "matrix-js-sdk/src/types"; import { createContext, useContext, useState, - ReactNode, + type ReactNode, useCallback, useEffect, useMemo, } from "react"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { logger } from "matrix-js-sdk/src/logger"; import { useMatrixRTCSessionMemberships } from "./useMatrixRTCSessionMemberships"; import { useClientState } from "./ClientContext"; import { - ECallReactionEventContent, + type ECallReactionEventContent, ElementCallReactionEventType, GenericReaction, - ReactionOption, + type ReactionOption, ReactionSet, } from "./reactions"; import { useLatest } from "./useLatest"; diff --git a/src/useReactiveState.ts b/src/useReactiveState.ts index 76d8d410..2a58d33a 100644 --- a/src/useReactiveState.ts +++ b/src/useReactiveState.ts @@ -6,9 +6,9 @@ Please see LICENSE in the repository root for full details. */ import { - DependencyList, - Dispatch, - SetStateAction, + type DependencyList, + type Dispatch, + type SetStateAction, useCallback, useRef, useState, diff --git a/src/useTheme.test.ts b/src/useTheme.test.ts index 31822668..d0927b35 100644 --- a/src/useTheme.test.ts +++ b/src/useTheme.test.ts @@ -11,7 +11,7 @@ import { beforeEach, describe, expect, - Mock, + type Mock, test, vi, } from "vitest"; diff --git a/src/utils/matrix.ts b/src/utils/matrix.ts index 63b6ef67..abc49295 100644 --- a/src/utils/matrix.ts +++ b/src/utils/matrix.ts @@ -9,12 +9,12 @@ import { IndexedDBStore } from "matrix-js-sdk/src/store/indexeddb"; import { MemoryStore } from "matrix-js-sdk/src/store/memory"; import { createClient, - ICreateClientOpts, + type ICreateClientOpts, Preset, Visibility, } from "matrix-js-sdk/src/matrix"; import { ClientEvent } from "matrix-js-sdk/src/client"; -import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync"; +import { type ISyncStateData, type SyncState } from "matrix-js-sdk/src/sync"; import { logger } from "matrix-js-sdk/src/logger"; import { secureRandomBase64Url } from "matrix-js-sdk/src/randomstring"; @@ -24,7 +24,10 @@ import IndexedDBWorker from "../IndexedDBWorker?worker"; import { generateUrlSearchParams, getUrlParams } from "../UrlParams"; import { Config } from "../config/Config"; import { E2eeType } from "../e2ee/e2eeType"; -import { EncryptionSystem, saveKeyForRoom } from "../e2ee/sharedKeyManagement"; +import { + type EncryptionSystem, + saveKeyForRoom, +} from "../e2ee/sharedKeyManagement"; export const fallbackICEServerAllowed = import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true"; diff --git a/src/utils/observable.ts b/src/utils/observable.ts index dc804941..a54c0293 100644 --- a/src/utils/observable.ts +++ b/src/utils/observable.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { Observable, defer, finalize, scan, startWith, tap } from "rxjs"; +import { type Observable, defer, finalize, scan, startWith, tap } from "rxjs"; const nothing = Symbol("nothing"); diff --git a/src/utils/spa.ts b/src/utils/spa.ts index 37835259..ab3dbea5 100644 --- a/src/utils/spa.ts +++ b/src/utils/spa.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { ICreateClientOpts } from "matrix-js-sdk/src/client"; +import { type ICreateClientOpts } from "matrix-js-sdk/src/client"; import { MatrixError } from "matrix-js-sdk/src/http-api"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/utils/test.ts b/src/utils/test.ts index 459a252e..ca6a5fce 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -4,29 +4,29 @@ Copyright 2023, 2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { map, Observable, of, SchedulerLike } from "rxjs"; -import { RunHelpers, TestScheduler } from "rxjs/testing"; +import { map, type Observable, of, type SchedulerLike } from "rxjs"; +import { type RunHelpers, TestScheduler } from "rxjs/testing"; import { expect, vi } from "vitest"; import { - RoomMember, - Room as MatrixRoom, + type RoomMember, + type Room as MatrixRoom, MatrixEvent, - Room, + type Room, TypedEventEmitter, } from "matrix-js-sdk/src/matrix"; import { CallMembership, - Focus, + type Focus, MatrixRTCSessionEvent, - MatrixRTCSessionEventHandlerMap, - SessionMembershipData, + type MatrixRTCSessionEventHandlerMap, + type SessionMembershipData, } from "matrix-js-sdk/src/matrixrtc"; import { - LocalParticipant, - LocalTrackPublication, - RemoteParticipant, - RemoteTrackPublication, - Room as LivekitRoom, + type LocalParticipant, + type LocalTrackPublication, + type RemoteParticipant, + type RemoteTrackPublication, + type Room as LivekitRoom, } from "livekit-client"; import { @@ -34,7 +34,10 @@ import { RemoteUserMediaViewModel, } from "../state/MediaViewModel"; import { E2eeType } from "../e2ee/e2eeType"; -import { DEFAULT_CONFIG, ResolvedConfigOptions } from "../config/ConfigOptions"; +import { + DEFAULT_CONFIG, + type ResolvedConfigOptions, +} from "../config/ConfigOptions"; import { Config } from "../config/Config"; export function withFakeTimers(continuation: () => void): void { diff --git a/src/utils/testReactions.tsx b/src/utils/testReactions.tsx index fec3e859..6fad030c 100644 --- a/src/utils/testReactions.tsx +++ b/src/utils/testReactions.tsx @@ -5,27 +5,27 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { PropsWithChildren, ReactNode } from "react"; +import { type PropsWithChildren, type ReactNode } from "react"; import { randomUUID } from "crypto"; import EventEmitter from "events"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; import { EventType, RoomEvent, RelationType } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, EventTimeline, EventTimelineSet, - Room, + type Room, } from "matrix-js-sdk/src/matrix"; import { - MatrixRTCSession, + type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/src/matrixrtc"; import { ReactionsProvider } from "../useReactions"; import { - ECallReactionEventContent, + type ECallReactionEventContent, ElementCallReactionEventType, - ReactionOption, + type ReactionOption, } from "../reactions"; export const TestReactionsWrapper = ({ From 54149a496cc5678d66daedfd1e8909d3c8db6979 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 11 Dec 2024 09:36:59 +0000 Subject: [PATCH 39/72] Simplify settings tabs (#2875) * Simplify settings tabs - Move Analytics to Feedback tab - Rename Developer Settings to Developer Mode and move to Preferences tab - Clean up Preferences tab - Only way to see version number is by enabling Developer Mode * Remove extra label * Lint * Lint * Update locales/en/app.json Co-authored-by: Robin --------- Co-authored-by: Robin --- locales/en/app.json | 11 +-- src/settings/DeveloperSettingsTab.tsx | 75 ++++++++++++++ src/settings/FeedbackSettingsTab.tsx | 30 +++++- src/settings/PreferencesSettingsTab.tsx | 21 +++- src/settings/SettingsModal.tsx | 126 ++---------------------- 5 files changed, 136 insertions(+), 127 deletions(-) create mode 100644 src/settings/DeveloperSettingsTab.tsx diff --git a/locales/en/app.json b/locales/en/app.json index 1c1f7ecc..bcfc0945 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -150,8 +150,6 @@ "effect_volume_description": "Adjust the volume at which reactions and hand raised effects play.", "effect_volume_label": "Sound effect volume" }, - "developer_settings_label": "Developer Settings", - "developer_settings_label_description": "Expose developer settings in the settings window.", "developer_tab_title": "Developer", "feedback_tab_body": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.", "feedback_tab_description_label": "Your feedback", @@ -159,18 +157,17 @@ "feedback_tab_send_logs_label": "Include debug logs", "feedback_tab_thank_you": "Thanks, we received your feedback!", "feedback_tab_title": "Feedback", - "more_tab_title": "More", "opt_in_description": "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.", "preferences_tab": { + "developer_mode_label": "Developer mode", + "developer_mode_label_description": "Enable developer mode and show developer settings tab.", "reactions_play_sound_description": "Play a sound effect when anyone sends a reaction into a call.", "reactions_play_sound_label": "Play reaction sounds", "reactions_show_description": "Show an animation when anyone sends a reaction.", - "reactions_show_label": "Show reactions", - "reactions_title": "Reactions" + "reactions_show_label": "Show reactions" }, "preferences_tab_body": "Here you can configure extra options for an improved experience.", - "preferences_tab_h4": "Preferences", - "preferences_tab_show_hand_raised_timer_description": "Show a timer when a participant raises their hand.", + "preferences_tab_show_hand_raised_timer_description": "Show a timer when a participant raises their hand", "preferences_tab_show_hand_raised_timer_label": "Show hand raise duration", "speaker_device_selection_label": "Speaker" }, diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx new file mode 100644 index 00000000..0a33d52f --- /dev/null +++ b/src/settings/DeveloperSettingsTab.tsx @@ -0,0 +1,75 @@ +/* +Copyright 2022-2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { type ChangeEvent, type FC, useCallback } from "react"; +import { useTranslation } from "react-i18next"; + +import { FieldRow, InputField } from "../input/Input"; +import { + useSetting, + duplicateTiles as duplicateTilesSetting, +} from "./settings"; +import type { MatrixClient } from "matrix-js-sdk/src/client"; + +interface Props { + client: MatrixClient; +} + +export const DeveloperSettingsTab: FC = ({ client }) => { + const { t } = useTranslation(); + const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting); + + return ( + <> +

+ {t("developer_mode.hostname", { + hostname: window.location.hostname || "unknown", + })} +

+

+ {t("version", { + productName: import.meta.env.VITE_PRODUCT_NAME || "Element Call", + version: import.meta.env.VITE_APP_VERSION || "dev", + })} +

+

+ {t("developer_mode.crypto_version", { + version: client.getCrypto()?.getVersion() || "unknown", + })} +

+

+ {t("developer_mode.matrix_id", { + id: client.getUserId() || "unknown", + })} +

+

+ {t("developer_mode.device_id", { + id: client.getDeviceId() || "unknown", + })} +

+ + ): void => { + const value = event.target.valueAsNumber; + if (value < 0) { + return; + } + setDuplicateTiles(Number.isNaN(value) ? 0 : value); + }, + [setDuplicateTiles], + )} + /> + + + ); +}; diff --git a/src/settings/FeedbackSettingsTab.tsx b/src/settings/FeedbackSettingsTab.tsx index f77da898..78a116cd 100644 --- a/src/settings/FeedbackSettingsTab.tsx +++ b/src/settings/FeedbackSettingsTab.tsx @@ -5,15 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { type FC, useCallback } from "react"; +import { type ChangeEvent, type FC, useCallback } from "react"; import { randomString } from "matrix-js-sdk/src/randomstring"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { Button, Text } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake"; import feedbackStyles from "../input/FeedbackInput.module.css"; +import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; +import { useOptInAnalytics } from "./settings"; interface Props { roomId?: string; @@ -52,8 +54,32 @@ export const FeedbackSettingsTab: FC = ({ roomId }) => { [submitRageshake, roomId, sendRageshakeRequest], ); + const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics(); + const optInDescription = ( + + + +
+ You may withdraw consent by unchecking this box. If you are currently in + a call, this setting will take effect at the end of the call. +
+
+ ); + return (
+

{t("common.analytics")}

+ + ): void => { + setOptInAnalytics?.(event.target.checked); + }} + /> +

{t("settings.feedback_tab_h4")}

{t("settings.feedback_tab_body")}
diff --git a/src/settings/PreferencesSettingsTab.tsx b/src/settings/PreferencesSettingsTab.tsx index 32200f7b..bdf021b4 100644 --- a/src/settings/PreferencesSettingsTab.tsx +++ b/src/settings/PreferencesSettingsTab.tsx @@ -14,6 +14,7 @@ import { showHandRaisedTimer as showHandRaisedTimerSetting, showReactions as showReactionsSetting, playReactionsSound as playReactionsSoundSetting, + developerSettingsTab as developerSettingsTabSetting, useSetting, } from "./settings"; @@ -36,9 +37,12 @@ export const PreferencesSettingsTab: FC = () => { fn(e.target.checked); }; + const [developerSettingsTab, setDeveloperSettingsTab] = useSetting( + developerSettingsTabSetting, + ); + return (
-

{t("settings.preferences_tab_h4")}

{t("settings.preferences_tab_body")} { onChange={(e) => onChangeSetting(e, setShowHandRaisedTimer)} /> -
{t("settings.preferences_tab.reactions_title")}
{ onChange={(e) => onChangeSetting(e, setPlayReactionSound)} /> + + ): void => + setDeveloperSettingsTab(event.target.checked) + } + /> +
); }; diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 6bb8d42b..f3936a31 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -5,16 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { type ChangeEvent, type FC, useCallback, useState } from "react"; -import { Trans, useTranslation } from "react-i18next"; +import { type FC, useState } from "react"; +import { useTranslation } from "react-i18next"; import { type MatrixClient } from "matrix-js-sdk/src/matrix"; -import { Root as Form, Text } from "@vector-im/compound-web"; +import { Root as Form } from "@vector-im/compound-web"; import { Modal } from "../Modal"; import styles from "./SettingsModal.module.css"; import { type Tab, TabContainer } from "../tabs/Tabs"; -import { FieldRow, InputField } from "../input/Input"; -import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; import { ProfileSettingsTab } from "./ProfileSettingsTab"; import { FeedbackSettingsTab } from "./FeedbackSettingsTab"; import { @@ -24,15 +22,14 @@ import { import { widget } from "../widget"; import { useSetting, - developerSettingsTab as developerSettingsTabSetting, - duplicateTiles as duplicateTilesSetting, - useOptInAnalytics, soundEffectVolumeSetting, + developerSettingsTab, } from "./settings"; import { isFirefox } from "../Platform"; import { PreferencesSettingsTab } from "./PreferencesSettingsTab"; import { Slider } from "../Slider"; import { DeviceSelection } from "./DeviceSelection"; +import { DeveloperSettingsTab } from "./DeveloperSettingsTab"; type SettingsTab = | "audio" @@ -64,28 +61,13 @@ export const SettingsModal: FC = ({ }) => { const { t } = useTranslation(); - const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics(); - const [developerSettingsTab, setDeveloperSettingsTab] = useSetting( - developerSettingsTabSetting, - ); - const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting); - - const optInDescription = ( - - - -
- You may withdraw consent by unchecking this box. If you are currently in - a call, this setting will take effect at the end of the call. -
-
- ); - const devices = useMediaDevices(); useMediaDeviceNames(devices, open); const [soundVolume, setSoundVolume] = useSetting(soundEffectVolumeSetting); const [soundVolumeRaw, setSoundVolumeRaw] = useState(soundVolume); + const [showDeveloperSettingsTab] = useSetting(developerSettingsTab); + const audioTab: Tab = { key: "audio", name: t("common.audio"), @@ -151,104 +133,16 @@ export const SettingsModal: FC = ({ content: , }; - const moreTab: Tab = { - key: "more", - name: t("settings.more_tab_title"), - content: ( - <> -

{t("settings.developer_tab_title")}

-

- {t("version", { - productName: import.meta.env.VITE_PRODUCT_NAME || "Element Call", - version: import.meta.env.VITE_APP_VERSION || "dev", - })} -

- - ): void => - setDeveloperSettingsTab(event.target.checked) - } - /> - -

{t("common.analytics")}

- - ): void => { - setOptInAnalytics?.(event.target.checked); - }} - /> - - - ), - }; - const developerTab: Tab = { key: "developer", name: t("settings.developer_tab_title"), - content: ( - <> -

- {t("developer_mode.hostname", { - hostname: window.location.hostname || "unknown", - })} -

-

- {t("version", { - productName: import.meta.env.VITE_PRODUCT_NAME || "Element Call", - version: import.meta.env.VITE_APP_VERSION || "dev", - })} -

-

- {t("developer_mode.crypto_version", { - version: client.getCrypto()?.getVersion() || "unknown", - })} -

-

- {t("developer_mode.matrix_id", { - id: client.getUserId() || "unknown", - })} -

-

- {t("developer_mode.device_id", { - id: client.getDeviceId() || "unknown", - })} -

- - ): void => { - const value = event.target.valueAsNumber; - if (value < 0) { - return; - } - setDuplicateTiles(Number.isNaN(value) ? 0 : value); - }, - [setDuplicateTiles], - )} - /> - - - ), + content: , }; const tabs = [audioTab, videoTab]; if (widget === null) tabs.push(profileTab); - tabs.push(preferencesTab, feedbackTab, moreTab); - if (developerSettingsTab) tabs.push(developerTab); + tabs.push(preferencesTab, feedbackTab); + if (showDeveloperSettingsTab) tabs.push(developerTab); return ( Date: Wed, 11 Dec 2024 05:23:42 -0500 Subject: [PATCH 40/72] Add some quick-and-dirty debug info for TileStore (#2887) * Add some quick-and-dirty debug info for TileStore I'm still in need of more detailed data in order to understand why big layout shifts happen in large calls. This adds a developer option to enable logging and a visual indicator for the state of the TileStore. The indicator should be useful for matching up the behavior I'm seeing in my recordings with the right timestamps. * Reduce performance impact of checking for whether debug mode is enabled --------- Co-authored-by: Hugh Nimmo-Smith --- locales/en/app.json | 1 + src/room/InCallView.tsx | 10 ++++++ src/settings/DeveloperSettingsTab.tsx | 15 +++++++++ src/settings/settings.ts | 2 ++ src/state/CallViewModel.ts | 23 +++++++++++--- src/state/TileStore.ts | 46 +++++++++++++++++++++++++-- 6 files changed, 91 insertions(+), 6 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index bcfc0945..0d2b5f15 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -70,6 +70,7 @@ }, "developer_mode": { "crypto_version": "Crypto version: {{version}}", + "debug_tile_layout_label": "Debug tile layout", "device_id": "Device ID: {{id}}", "duplicate_tiles_label": "Number of additional tile copies per participant", "hostname": "Hostname: {{hostname}}", diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 6ee9f184..976e4e94 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -88,6 +88,10 @@ import { ReactionsAudioRenderer } from "./ReactionAudioRenderer"; import { useSwitchCamera } from "./useSwitchCamera"; import { ReactionsOverlay } from "./ReactionsOverlay"; import { CallEventAudioRenderer } from "./CallEventAudioRenderer"; +import { + debugTileLayout as debugTileLayoutSetting, + useSetting, +} from "../settings/settings"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); @@ -223,6 +227,8 @@ export const InCallView: FC = ({ const windowMode = useObservableEagerState(vm.windowMode); const layout = useObservableEagerState(vm.layout); + const tileStoreGeneration = useObservableEagerState(vm.tileStoreGeneration); + const [debugTileLayout] = useSetting(debugTileLayoutSetting); const gridMode = useObservableEagerState(vm.gridMode); const showHeader = useObservableEagerState(vm.showHeader); const showFooter = useObservableEagerState(vm.showFooter); @@ -585,6 +591,10 @@ export const InCallView: FC = ({ height={11} aria-label={import.meta.env.VITE_PRODUCT_NAME || "Element Call"} /> + {/* Don't mind this odd placement, it's just a little debug label */} + {debugTileLayout + ? `Tiles generation: ${tileStoreGeneration}` + : undefined}
)} {showControls &&
{buttons}
} diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index 0a33d52f..209bc41e 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -12,6 +12,7 @@ import { FieldRow, InputField } from "../input/Input"; import { useSetting, duplicateTiles as duplicateTilesSetting, + debugTileLayout as debugTileLayoutSetting, } from "./settings"; import type { MatrixClient } from "matrix-js-sdk/src/client"; @@ -22,6 +23,9 @@ interface Props { export const DeveloperSettingsTab: FC = ({ client }) => { const { t } = useTranslation(); const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting); + const [debugTileLayout, setDebugTileLayout] = useSetting( + debugTileLayoutSetting, + ); return ( <> @@ -70,6 +74,17 @@ export const DeveloperSettingsTab: FC = ({ client }) => { )} /> + + ): void => + setDebugTileLayout(event.target.checked) + } + /> + ); }; diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 8d32adbe..63c38a57 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -75,6 +75,8 @@ export const developerSettingsTab = new Setting( export const duplicateTiles = new Setting("duplicate-tiles", 0); +export const debugTileLayout = new Setting("debug-tile-layout", false); + export const audioInput = new Setting( "audio-input", undefined, diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index a2a10884..72712aa9 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -891,10 +891,9 @@ export class CallViewModel extends ViewModel { this.scope.state(), ); - /** - * The layout of tiles in the call interface. - */ - public readonly layout: Observable = this.layoutMedia.pipe( + public readonly layoutInternals: Observable< + LayoutScanState & { layout: Layout } + > = this.layoutMedia.pipe( // Each layout will produce a set of tiles, and these tiles have an // observable indicating whether they're visible. We loop this information // back into the layout process by using switchScan. @@ -949,10 +948,26 @@ export class CallViewModel extends ViewModel { visibleTiles: new Set(), }, ), + this.scope.state(), + ); + + /** + * The layout of tiles in the call interface. + */ + public readonly layout: Observable = this.layoutInternals.pipe( map(({ layout }) => layout), this.scope.state(), ); + /** + * The current generation of the tile store, exposed for debugging purposes. + */ + public readonly tileStoreGeneration: Observable = + this.layoutInternals.pipe( + map(({ tiles }) => tiles.generation), + this.scope.state(), + ); + public showSpotlightIndicators: Observable = this.layout.pipe( map((l) => l.type !== "grid"), this.scope.state(), diff --git a/src/state/TileStore.ts b/src/state/TileStore.ts index ab918cb0..2464d9eb 100644 --- a/src/state/TileStore.ts +++ b/src/state/TileStore.ts @@ -6,10 +6,19 @@ Please see LICENSE in the repository root for full details. */ import { BehaviorSubject } from "rxjs"; +import { logger } from "matrix-js-sdk/src/logger"; import { type MediaViewModel, type UserMediaViewModel } from "./MediaViewModel"; import { GridTileViewModel, SpotlightTileViewModel } from "./TileViewModel"; import { fillGaps } from "../utils/iter"; +import { debugTileLayout } from "../settings/settings"; + +function debugEntries(entries: GridTileData[]): string[] { + return entries.map((e) => e.media.member?.rawDisplayName ?? "[👻]"); +} + +let DEBUG_ENABLED = false; +debugTileLayout.value.subscribe((value) => (DEBUG_ENABLED = value)); class SpotlightTileData { private readonly media_: BehaviorSubject; @@ -69,6 +78,10 @@ export class TileStore { private constructor( private readonly spotlight: SpotlightTileData | null, private readonly grid: GridTileData[], + /** + * A number incremented on each update, just for debugging purposes. + */ + public readonly generation: number, ) {} public readonly spotlightTile = this.spotlight?.vm; @@ -81,7 +94,7 @@ export class TileStore { * Creates an an empty collection of tiles. */ public static empty(): TileStore { - return new TileStore(null, []); + return new TileStore(null, [], 0); } /** @@ -92,8 +105,9 @@ export class TileStore { return new TileStoreBuilder( this.spotlight, this.grid, - (spotlight, grid) => new TileStore(spotlight, grid), + (spotlight, grid) => new TileStore(spotlight, grid, this.generation + 1), visibleTiles, + this.generation, ); } } @@ -133,6 +147,10 @@ export class TileStoreBuilder { grid: GridTileData[], ) => TileStore, private readonly visibleTiles: Set, + /** + * A number incremented on each update, just for debugging purposes. + */ + private readonly generation: number, ) {} /** @@ -140,6 +158,11 @@ export class TileStoreBuilder { * will be no spotlight tile. */ public registerSpotlight(media: MediaViewModel[], maximised: boolean): void { + if (DEBUG_ENABLED) + logger.debug( + `[TileStore, ${this.generation}] register spotlight: ${media.map((m) => m.member?.rawDisplayName ?? "[👻]")}`, + ); + if (this.spotlight !== null) throw new Error("Spotlight already set"); if (this.numGridEntries > 0) throw new Error("Spotlight must be registered before grid tiles"); @@ -159,6 +182,11 @@ export class TileStoreBuilder { * media, then that media will have no grid tile. */ public registerGridTile(media: UserMediaViewModel): void { + if (DEBUG_ENABLED) + logger.debug( + `[TileStore, ${this.generation}] register grid tile: ${media.member?.rawDisplayName ?? "[👻]"}`, + ); + if (this.spotlight !== null) { // We actually *don't* want spotlight speakers to appear in both the // spotlight and the grid, so they're filtered out here @@ -246,6 +274,20 @@ export class TileStoreBuilder { ...this.invisibleGridEntries, ]), ]; + if (DEBUG_ENABLED) { + logger.debug( + `[TileStore, ${this.generation}] stationary: ${debugEntries(this.stationaryGridEntries)}`, + ); + logger.debug( + `[TileStore, ${this.generation}] visible: ${debugEntries(this.visibleGridEntries)}`, + ); + logger.debug( + `[TileStore, ${this.generation}] invisible: ${debugEntries(this.invisibleGridEntries)}`, + ); + logger.debug( + `[TileStore, ${this.generation}] result: ${debugEntries(grid)}`, + ); + } // Destroy unused tiles if (this.spotlight === null && this.prevSpotlight !== null) From 6c81f695908df923e7c43ca5bcd05a379256f132 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 11 Dec 2024 14:30:16 +0000 Subject: [PATCH 41/72] Rename developerSettingsTab to developerMode for consistency (#2889) --- src/settings/PreferencesSettingsTab.tsx | 10 ++++------ src/settings/SettingsModal.tsx | 4 ++-- src/settings/settings.ts | 5 +---- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/settings/PreferencesSettingsTab.tsx b/src/settings/PreferencesSettingsTab.tsx index bdf021b4..fd4be1ae 100644 --- a/src/settings/PreferencesSettingsTab.tsx +++ b/src/settings/PreferencesSettingsTab.tsx @@ -14,7 +14,7 @@ import { showHandRaisedTimer as showHandRaisedTimerSetting, showReactions as showReactionsSetting, playReactionsSound as playReactionsSoundSetting, - developerSettingsTab as developerSettingsTabSetting, + developerMode as developerModeSetting, useSetting, } from "./settings"; @@ -37,9 +37,7 @@ export const PreferencesSettingsTab: FC = () => { fn(e.target.checked); }; - const [developerSettingsTab, setDeveloperSettingsTab] = useSetting( - developerSettingsTabSetting, - ); + const [developerMode, setDeveloperMode] = useSetting(developerModeSetting); return (
@@ -82,13 +80,13 @@ export const PreferencesSettingsTab: FC = () => { ): void => - setDeveloperSettingsTab(event.target.checked) + setDeveloperMode(event.target.checked) } /> diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index f3936a31..c1466e8a 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -23,7 +23,7 @@ import { widget } from "../widget"; import { useSetting, soundEffectVolumeSetting, - developerSettingsTab, + developerMode, } from "./settings"; import { isFirefox } from "../Platform"; import { PreferencesSettingsTab } from "./PreferencesSettingsTab"; @@ -66,7 +66,7 @@ export const SettingsModal: FC = ({ const [soundVolume, setSoundVolume] = useSetting(soundEffectVolumeSetting); const [soundVolumeRaw, setSoundVolumeRaw] = useState(soundVolume); - const [showDeveloperSettingsTab] = useSetting(developerSettingsTab); + const [showDeveloperSettingsTab] = useSetting(developerMode); const audioTab: Tab = { key: "audio", diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 63c38a57..e8dda379 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -68,10 +68,7 @@ export const useOptInAnalytics = (): [ return PosthogAnalytics.instance.isEnabled() ? setting : [false, null]; }; -export const developerSettingsTab = new Setting( - "developer-settings-tab", - false, -); +export const developerMode = new Setting("developer-settings-tab", false); export const duplicateTiles = new Setting("duplicate-tiles", 0); From 77facd01e41faae55a62dd7d839fbb351a881865 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 12 Dec 2024 07:33:47 +0000 Subject: [PATCH 42/72] Add support for playing a sound when the user exits a call. (#2860) * Refactor to use AudioContext * Remove unused audio format. * Reduce update frequency for volume * Port to useAudioContext * Port reactionaudiorenderer to useAudioContext * Integrate raise hand sound into call event renderer. * Simplify reaction sounds * only play one sound per reaction type * Start to build out tests * fixup tests / comments * Fix reaction sound * remove console line * Remove another debug line. * fix lint * Use testing library click * lint * Add support for playing a sound when the user exits a call. * Port GroupCallView to useAudioContext * Remove debug bits. * asyncify * lint * lint * lint * tidy * Add test for group call view * Test widget mode too. * fix ?. * Format * Lint * Lint --------- Co-authored-by: Hugh Nimmo-Smith --- src/room/CallEventAudioRenderer.tsx | 10 +- src/room/GroupCallView.test.tsx | 153 ++++++++++++++++++++++++++++ src/room/GroupCallView.tsx | 74 +++++++++----- src/room/ReactionAudioRenderer.tsx | 4 +- src/room/RoomPage.tsx | 1 + src/rtcSessionHelpers.ts | 8 +- src/useAudioContext.test.tsx | 7 +- src/useAudioContext.tsx | 11 +- src/utils/test.ts | 12 ++- 9 files changed, 242 insertions(+), 38 deletions(-) create mode 100644 src/room/GroupCallView.test.tsx diff --git a/src/room/CallEventAudioRenderer.tsx b/src/room/CallEventAudioRenderer.tsx index 3c2e338f..a363c6f5 100644 --- a/src/room/CallEventAudioRenderer.tsx +++ b/src/room/CallEventAudioRenderer.tsx @@ -25,7 +25,7 @@ import { useLatest } from "../useLatest"; export const MAX_PARTICIPANT_COUNT_FOR_SOUND = 8; export const THROTTLE_SOUND_EFFECT_MS = 500; -const sounds = prefetchSounds({ +export const callEventAudioSounds = prefetchSounds({ join: { mp3: joinCallSoundMp3, ogg: joinCallSoundOgg, @@ -46,7 +46,7 @@ export function CallEventAudioRenderer({ vm: CallViewModel; }): ReactNode { const audioEngineCtx = useAudioContext({ - sounds, + sounds: callEventAudioSounds, latencyHint: "interactive", }); const audioEngineRef = useLatest(audioEngineCtx); @@ -60,7 +60,7 @@ export function CallEventAudioRenderer({ useEffect(() => { if (audioEngineRef.current && previousRaisedHandCount < raisedHandCount) { - audioEngineRef.current.playSound("raiseHand"); + void audioEngineRef.current.playSound("raiseHand"); } }, [audioEngineRef, previousRaisedHandCount, raisedHandCount]); @@ -74,7 +74,7 @@ export function CallEventAudioRenderer({ throttle(() => interval(THROTTLE_SOUND_EFFECT_MS)), ) .subscribe(() => { - audioEngineRef.current?.playSound("join"); + void audioEngineRef.current?.playSound("join"); }); const leftSub = vm.memberChanges @@ -86,7 +86,7 @@ export function CallEventAudioRenderer({ throttle(() => interval(THROTTLE_SOUND_EFFECT_MS)), ) .subscribe(() => { - audioEngineRef.current?.playSound("left"); + void audioEngineRef.current?.playSound("left"); }); return (): void => { diff --git a/src/room/GroupCallView.test.tsx b/src/room/GroupCallView.test.tsx new file mode 100644 index 00000000..ea2cc5cf --- /dev/null +++ b/src/room/GroupCallView.test.tsx @@ -0,0 +1,153 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { beforeEach, expect, type MockedFunction, test, vitest } from "vitest"; +import { render } from "@testing-library/react"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; +import { of } from "rxjs"; +import { JoinRule, type RoomState } from "matrix-js-sdk/src/matrix"; +import { Router } from "react-router-dom"; +import { createBrowserHistory } from "history"; +import userEvent from "@testing-library/user-event"; + +import { type MuteStates } from "./MuteStates"; +import { prefetchSounds } from "../soundUtils"; +import { useAudioContext } from "../useAudioContext"; +import { ActiveCall } from "./InCallView"; +import { + mockMatrixRoom, + mockMatrixRoomMember, + mockRtcMembership, + MockRTCSession, +} from "../utils/test"; +import { GroupCallView } from "./GroupCallView"; +import { leaveRTCSession } from "../rtcSessionHelpers"; +import { type WidgetHelpers } from "../widget"; +import { LazyEventEmitter } from "../LazyEventEmitter"; + +vitest.mock("../soundUtils"); +vitest.mock("../useAudioContext"); +vitest.mock("./InCallView"); + +vitest.mock("../rtcSessionHelpers", async (importOriginal) => { + // TODO: perhaps there is a more elegant way to manage the type import here? + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const orig = await importOriginal(); + vitest.spyOn(orig, "leaveRTCSession"); + return orig; +}); + +let playSound: MockedFunction< + NonNullable>["playSound"] +>; + +const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); +const carol = mockMatrixRoomMember(localRtcMember); +const roomMembers = new Map([carol].map((p) => [p.userId, p])); + +const roomId = "!foo:bar"; +const soundPromise = Promise.resolve(true); + +beforeEach(() => { + (prefetchSounds as MockedFunction).mockResolvedValue({ + sound: new ArrayBuffer(0), + }); + playSound = vitest.fn().mockReturnValue(soundPromise); + (useAudioContext as MockedFunction).mockReturnValue({ + playSound, + }); + // A trivial implementation of Active call to ensure we are testing GroupCallView exclusively here. + (ActiveCall as MockedFunction).mockImplementation( + ({ onLeave }) => { + return ( +
+ +
+ ); + }, + ); +}); + +function createGroupCallView(widget: WidgetHelpers | null): { + rtcSession: MockRTCSession; + getByText: ReturnType["getByText"]; +} { + const history = createBrowserHistory(); + const client = { + getUser: () => null, + getUserId: () => localRtcMember.sender, + getDeviceId: () => localRtcMember.deviceId, + getRoom: (rId) => (rId === roomId ? room : null), + } as Partial as MatrixClient; + const room = mockMatrixRoom({ + client, + roomId, + getMember: (userId) => roomMembers.get(userId) ?? null, + getMxcAvatarUrl: () => null, + getCanonicalAlias: () => null, + currentState: { + getJoinRule: () => JoinRule.Invite, + } as Partial as RoomState, + }); + const rtcSession = new MockRTCSession( + room, + localRtcMember, + [], + ).withMemberships(of([])); + const muteState = { + audio: { enabled: false }, + video: { enabled: false }, + } as MuteStates; + const { getByText } = render( + + + , + ); + return { + getByText, + rtcSession, + }; +} + +test("will play a leave sound asynchronously in SPA mode", async () => { + const user = userEvent.setup(); + const { getByText, rtcSession } = createGroupCallView(null); + const leaveButton = getByText("Leave"); + await user.click(leaveButton); + expect(playSound).toHaveBeenCalledWith("left"); + expect(leaveRTCSession).toHaveBeenCalledWith(rtcSession, undefined); + expect(rtcSession.leaveRoomSession).toHaveBeenCalledOnce(); +}); + +test("will play a leave sound synchronously in widget mode", async () => { + const user = userEvent.setup(); + const widget = { + api: { + setAlwaysOnScreen: async () => Promise.resolve(true), + } as Partial, + lazyActions: new LazyEventEmitter(), + }; + const { getByText, rtcSession } = createGroupCallView( + widget as WidgetHelpers, + ); + const leaveButton = getByText("Leave"); + await user.click(leaveButton); + expect(playSound).toHaveBeenCalledWith("left"); + expect(leaveRTCSession).toHaveBeenCalledWith(rtcSession, soundPromise); + expect(rtcSession.leaveRoomSession).toHaveBeenCalledOnce(); +}); diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 98bfa1a6..3ea6a9c2 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -26,7 +26,11 @@ import { Heading, Text } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; import type { IWidgetApiRequest } from "matrix-widget-api"; -import { widget, ElementWidgetActions, type JoinCallData } from "../widget"; +import { + ElementWidgetActions, + type JoinCallData, + type WidgetHelpers, +} from "../widget"; import { FullScreenView } from "../FullScreenView"; import { LobbyView } from "./LobbyView"; import { type MatrixInfo } from "./VideoPreview"; @@ -51,6 +55,9 @@ import { InviteModal } from "./InviteModal"; import { useUrlParams } from "../UrlParams"; import { E2eeType } from "../e2ee/e2eeType"; import { Link } from "../button/Link"; +import { useAudioContext } from "../useAudioContext"; +import { callEventAudioSounds } from "./CallEventAudioRenderer"; +import { useLatest } from "../useLatest"; declare global { interface Window { @@ -67,6 +74,7 @@ interface Props { hideHeader: boolean; rtcSession: MatrixRTCSession; muteStates: MuteStates; + widget: WidgetHelpers | null; } export const GroupCallView: FC = ({ @@ -78,10 +86,16 @@ export const GroupCallView: FC = ({ hideHeader, rtcSession, muteStates, + widget, }) => { const memberships = useMatrixRTCSessionMemberships(rtcSession); const isJoined = useMatrixRTCSessionJoinState(rtcSession); - + const leaveSoundContext = useLatest( + useAudioContext({ + sounds: callEventAudioSounds, + latencyHint: "interactive", + }), + ); // This should use `useEffectEvent` (only available in experimental versions) useEffect(() => { if (memberships.length >= MUTE_PARTICIPANT_COUNT) @@ -195,14 +209,14 @@ export const GroupCallView: FC = ({ ev.detail.data as unknown as JoinCallData, ); await enterRTCSession(rtcSession, perParticipantE2EE); - widget!.api.transport.reply(ev.detail, {}); + widget.api.transport.reply(ev.detail, {}); })().catch((e) => { logger.error("Error joining RTC session", e); }); }; widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin); return (): void => { - widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin); + widget.lazyActions.off(ElementWidgetActions.JoinCall, onJoin); }; } else { // No lobby and no preload: we enter the rtc session right away @@ -216,7 +230,7 @@ export const GroupCallView: FC = ({ void enterRTCSession(rtcSession, perParticipantE2EE); } } - }, [rtcSession, preload, skipLobby, perParticipantE2EE]); + }, [widget, rtcSession, preload, skipLobby, perParticipantE2EE]); const [left, setLeft] = useState(false); const [leaveError, setLeaveError] = useState(undefined); @@ -224,12 +238,12 @@ export const GroupCallView: FC = ({ const onLeave = useCallback( (leaveError?: Error): void => { - setLeaveError(leaveError); - setLeft(true); - + const audioPromise = leaveSoundContext.current?.playSound("left"); // In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent, // therefore we want the event to be sent instantly without getting queued/batched. const sendInstantly = !!widget; + setLeaveError(leaveError); + setLeft(true); PosthogAnalytics.instance.eventCallEnded.track( rtcSession.room.roomId, rtcSession.memberships.length, @@ -237,8 +251,12 @@ export const GroupCallView: FC = ({ rtcSession, ); - // Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts. - leaveRTCSession(rtcSession) + leaveRTCSession( + rtcSession, + // Wait for the sound in widget mode (it's not long) + sendInstantly && audioPromise ? audioPromise : undefined, + ) + // Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts. .then(() => { if ( !isPasswordlessUser && @@ -252,18 +270,25 @@ export const GroupCallView: FC = ({ logger.error("Error leaving RTC session", e); }); }, - [rtcSession, isPasswordlessUser, confineToRoom, history], + [ + widget, + rtcSession, + isPasswordlessUser, + confineToRoom, + leaveSoundContext, + history, + ], ); useEffect(() => { if (widget && isJoined) { // set widget to sticky once joined. - widget!.api.setAlwaysOnScreen(true).catch((e) => { + widget.api.setAlwaysOnScreen(true).catch((e) => { logger.error("Error calling setAlwaysOnScreen(true)", e); }); const onHangup = (ev: CustomEvent): void => { - widget!.api.transport.reply(ev.detail, {}); + widget.api.transport.reply(ev.detail, {}); // Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts. leaveRTCSession(rtcSession).catch((e) => { logger.error("Failed to leave RTC session", e); @@ -271,10 +296,10 @@ export const GroupCallView: FC = ({ }; widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup); return (): void => { - widget!.lazyActions.off(ElementWidgetActions.HangupCall, onHangup); + widget.lazyActions.off(ElementWidgetActions.HangupCall, onHangup); }; } - }, [isJoined, rtcSession]); + }, [widget, isJoined, rtcSession]); const onReconnect = useCallback(() => { setLeft(false); @@ -367,14 +392,17 @@ export const GroupCallView: FC = ({ leaveError ) { return ( - + <> + + ; + ); } else { // If the user is a regular user, we'll have sent them back to the homepage, diff --git a/src/room/ReactionAudioRenderer.tsx b/src/room/ReactionAudioRenderer.tsx index 6463c9d1..be24a5d6 100644 --- a/src/room/ReactionAudioRenderer.tsx +++ b/src/room/ReactionAudioRenderer.tsx @@ -60,10 +60,10 @@ export function ReactionsAudioRenderer(): ReactNode { return; } if (soundMap[reactionName]) { - audioEngineRef.current.playSound(reactionName); + void audioEngineRef.current.playSound(reactionName); } else { // Fallback sounds. - audioEngineRef.current.playSound("generic"); + void audioEngineRef.current.playSound("generic"); } } }, [audioEngineRef, shouldPlay, oldReactions, reactions]); diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx index 8c88b985..d8973c20 100644 --- a/src/room/RoomPage.tsx +++ b/src/room/RoomPage.tsx @@ -98,6 +98,7 @@ export const RoomPage: FC = () => { case "loaded": return ( , ): Promise => { // we need to wait until the callEnded event is tracked on posthog. // Otherwise the iFrame gets killed before the callEnded event got tracked. @@ -132,6 +133,8 @@ const widgetPostHangupProcedure = async ( logger.error("Failed to set call widget `alwaysOnScreen` to false", e); } + // Wait for any last bits before hanging up. + await promiseBeforeHangup; // We send the hangup event after the memberships have been updated // calling leaveRTCSession. // We need to wait because this makes the client hosting this widget killing the IFrame. @@ -140,9 +143,12 @@ const widgetPostHangupProcedure = async ( export async function leaveRTCSession( rtcSession: MatrixRTCSession, + promiseBeforeHangup?: Promise, ): Promise { await rtcSession.leaveRoomSession(); if (widget) { - await widgetPostHangupProcedure(widget); + await widgetPostHangupProcedure(widget, promiseBeforeHangup); + } else { + await promiseBeforeHangup; } } diff --git a/src/useAudioContext.test.tsx b/src/useAudioContext.test.tsx index 2b9b0982..565208b1 100644 --- a/src/useAudioContext.test.tsx +++ b/src/useAudioContext.test.tsx @@ -29,9 +29,11 @@ const TestComponent: FC = () => { } return ( <> - + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/} - @@ -61,6 +63,7 @@ class MockAudioContext { vitest.mocked({ connect: (v: unknown) => v, start: () => {}, + addEventListener: (_name: string, cb: () => void) => cb(), }), ); public createGain = vitest.fn().mockReturnValue(this.gain); diff --git a/src/useAudioContext.tsx b/src/useAudioContext.tsx index 1580f32a..656b7460 100644 --- a/src/useAudioContext.tsx +++ b/src/useAudioContext.tsx @@ -22,18 +22,21 @@ import { type PrefetchedSounds } from "./soundUtils"; * @param volume The volume to play at. * @param ctx The context to play through. * @param buffer The buffer to play. + * @returns A promise that resolves when the sound has finished playing. */ -function playSound( +async function playSound( ctx: AudioContext, buffer: AudioBuffer, volume: number, -): void { +): Promise { const gain = ctx.createGain(); gain.gain.setValueAtTime(volume, 0); const src = ctx.createBufferSource(); src.buffer = buffer; src.connect(gain).connect(ctx.destination); + const p = new Promise((r) => src.addEventListener("ended", () => r())); src.start(); + return p; } interface Props { @@ -47,7 +50,7 @@ interface Props { } interface UseAudioContext { - playSound(soundName: S): void; + playSound(soundName: S): Promise; } /** @@ -113,7 +116,7 @@ export function useAudioContext( return null; } return { - playSound: (name): void => { + playSound: async (name): Promise => { if (!audioBuffers[name]) { logger.debug(`Tried to play a sound that wasn't buffered (${name})`); return; diff --git a/src/utils/test.ts b/src/utils/test.ts index ca6a5fce..1cd21f01 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { map, type Observable, of, type SchedulerLike } from "rxjs"; import { type RunHelpers, TestScheduler } from "rxjs/testing"; -import { expect, vi } from "vitest"; +import { expect, vi, vitest } from "vitest"; import { type RoomMember, type Room as MatrixRoom, @@ -258,6 +258,12 @@ export class MockRTCSession extends TypedEventEmitter< MatrixRTCSessionEvent, MatrixRTCSessionEventHandlerMap > { + public readonly statistics = { + counters: {}, + }; + + public leaveRoomSession = vitest.fn().mockResolvedValue(undefined); + public constructor( public readonly room: Room, private localMembership: CallMembership, @@ -266,6 +272,10 @@ export class MockRTCSession extends TypedEventEmitter< super(); } + public isJoined(): true { + return true; + } + public withMemberships( rtcMembers: Observable[]>, ): MockRTCSession { From 4d6740cf2c34e8c888b2cc129fda40fa31d91703 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:40:55 +0000 Subject: [PATCH 43/72] Update dependency livekit-client to v2.7.5 --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index d1e74792..75c2ef72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1954,10 +1954,10 @@ resolved "https://registry.yarnpkg.com/@livekit/mutex/-/mutex-1.0.0.tgz#9493102d92ff75dfb0445eccc46c7c7ac189d385" integrity sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw== -"@livekit/protocol@1.24.0": - version "1.24.0" - resolved "https://registry.yarnpkg.com/@livekit/protocol/-/protocol-1.24.0.tgz#b23acab25c11027bf26c1b42f9b782682f2da585" - integrity sha512-9dCsqnkMn7lvbI4NGh18zhLDsrXyUcpS++TEFgEk5Xv1WM3R2kT3EzqgL1P/mr3jaabM6rJ8wZA/KJLuQNpF5w== +"@livekit/protocol@1.29.4": + version "1.29.4" + resolved "https://registry.yarnpkg.com/@livekit/protocol/-/protocol-1.29.4.tgz#346906d080bc8207a80570b45db91153a495e0dc" + integrity sha512-dsqxvABHilrMA0BU5m1w8cMWSVeDjV2ZUIUDClNQZju3c30DLMfEYDHU5nmXDfaaHjNIgoRbYR7upJMozG8JJg== dependencies: "@bufbuild/protobuf" "^1.10.0" @@ -6351,12 +6351,12 @@ lines-and-columns@^1.1.6: integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== livekit-client@^2.5.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-2.7.0.tgz#d7a80aff4ad335dd093b0c90d0d715466539651a" - integrity sha512-4vjfSReFNAUD+2oLUz9qFRWztJaI/+AexpOmCgizNsPYpvvqgAvEGxapnhuAug9uP7JVYaKPXaTCq90MWZoDHg== + version "2.7.5" + resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-2.7.5.tgz#2c8e5956c1fda5844799f5a864ac87c803ca1a43" + integrity sha512-sPhHYwXvG75y1LDC50dDC9k6Z49L2vc/HcMRhzhi7yBca6ofPEebpB0bmPOry4ovrnFA+a8TL1pFR2mko1/clw== dependencies: "@livekit/mutex" "1.0.0" - "@livekit/protocol" "1.24.0" + "@livekit/protocol" "1.29.4" events "^3.3.0" loglevel "^1.8.0" sdp-transform "^2.14.1" From 00056a7cd9ce4f691c36d455416b1aca964b9944 Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 12 Dec 2024 17:32:13 -0500 Subject: [PATCH 44/72] Determine which tiles are on screen in a more stable manner Instead of tracking for each individual tile whether it's visible, just track the total number of tiles that appear on screen. This ought to make the whole thing a lot less dynamic, which is crucial given that our UI renders asynchronously and RxJS doesn't really support cyclic dependencies in any rigorous way. In particular this ought to make the following kind of situation impossible: 1. There 3 tiles, ABC. A and B are on screen. 2. Now C becomes important. The requested order is now CAB. 3. To reduce the size of the layout shift, the algorithm selects to swap just B and C in the original order, giving ACB. However, the UI is blocked and doesn't render this order yet. 4. For whatever reason, a spurious update of the importance algorithm occurs. It once again requests CAB. 5. Now because the UI was blocked, the layout still thinks that A and B are on screen (rather than A and C). It thinks that C is some weird island of "off-screen territory" in the middle of the tile order. This confuses it into swapping A and C rather than keeping the layout stable. The reality is that whenever we think N tiles are visible on screen, we're always referring to the first N tiles in the grid. It's best if the code reflects this assumption. --- src/grid/Grid.tsx | 80 ++++++++++++++------------- src/grid/GridLayout.tsx | 11 +--- src/grid/OneOnOneLayout.tsx | 2 - src/grid/SpotlightExpandedLayout.tsx | 1 - src/grid/SpotlightLandscapeLayout.tsx | 11 +--- src/grid/SpotlightPortraitLayout.tsx | 11 +--- src/state/CallViewModel.test.ts | 5 +- src/state/CallViewModel.ts | 65 ++++++++++------------ src/state/GridLikeLayout.ts | 5 +- src/state/OneOnOneLayout.ts | 4 +- src/state/PipLayout.ts | 4 +- src/state/SpotlightExpandedLayout.ts | 4 +- src/state/TileStore.ts | 18 +++--- src/state/TileViewModel.ts | 10 +--- 14 files changed, 96 insertions(+), 135 deletions(-) diff --git a/src/grid/Grid.tsx b/src/grid/Grid.tsx index 411d9d08..268d4352 100644 --- a/src/grid/Grid.tsx +++ b/src/grid/Grid.tsx @@ -24,7 +24,6 @@ import { createContext, forwardRef, memo, - useCallback, useContext, useEffect, useMemo, @@ -54,7 +53,6 @@ interface Tile { id: string; model: Model; onDrag: DragCallback | undefined; - setVisible: (visible: boolean) => void; } type PlacedTile = Tile & Rect; @@ -88,7 +86,6 @@ interface SlotProps extends Omit, "onDrag"> { id: string; model: Model; onDrag?: DragCallback; - onVisibilityChange?: (visible: boolean) => void; style?: CSSProperties; className?: string; } @@ -115,24 +112,47 @@ function offset(element: HTMLElement, relativeTo: Element): Offset { } } +export type VisibleTilesCallback = (visibleTiles: number) => void; + interface LayoutContext { setGeneration: Dispatch>; + setVisibleTilesCallback: Dispatch< + SetStateAction + >; } const LayoutContext = createContext(null); +function useLayoutContext(): LayoutContext { + const context = useContext(LayoutContext); + if (context === null) + throw new Error("useUpdateLayout called outside a Grid layout context"); + return context; +} + /** * Enables Grid to react to layout changes. You must call this in your Layout * component or else Grid will not be reactive. */ export function useUpdateLayout(): void { - const context = useContext(LayoutContext); - if (context === null) - throw new Error("useUpdateLayout called outside a Grid layout context"); - + const { setGeneration } = useLayoutContext(); // On every render, tell Grid that the layout may have changed - useEffect(() => - context.setGeneration((prev) => (prev === null ? 0 : prev + 1)), + useEffect(() => setGeneration((prev) => (prev === null ? 0 : prev + 1))); +} + +/** + * Asks Grid to call a callback whenever the number of visible tiles may have + * changed. + */ +export function useVisibleTiles(callback: VisibleTilesCallback): void { + const { setVisibleTilesCallback } = useLayoutContext(); + useEffect( + () => setVisibleTilesCallback(() => callback), + [callback, setVisibleTilesCallback], + ); + useEffect( + () => (): void => setVisibleTilesCallback(null), + [setVisibleTilesCallback], ); } @@ -245,39 +265,20 @@ export function Grid< const windowHeight = useObservableEagerState(windowHeightObservable); const [layoutRoot, setLayoutRoot] = useState(null); const [generation, setGeneration] = useState(null); + const [visibleTilesCallback, setVisibleTilesCallback] = + useState(null); const tiles = useInitial(() => new Map>()); const prefersReducedMotion = usePrefersReducedMotion(); const Slot: FC> = useMemo( () => - function Slot({ - id, - model, - onDrag, - onVisibilityChange, - style, - className, - ...props - }) { + function Slot({ id, model, onDrag, style, className, ...props }) { const ref = useRef(null); - const prevVisible = useRef(null); - const setVisible = useCallback( - (visible: boolean) => { - if ( - onVisibilityChange !== undefined && - visible !== prevVisible.current - ) { - onVisibilityChange(visible); - prevVisible.current = visible; - } - }, - [onVisibilityChange], - ); useEffect(() => { - tiles.set(id, { id, model, onDrag, setVisible }); + tiles.set(id, { id, model, onDrag }); return (): void => void tiles.delete(id); - }, [id, model, onDrag, setVisible]); + }, [id, model, onDrag]); return (
({ setGeneration }), []); + const context: LayoutContext = useMemo( + () => ({ setGeneration, setVisibleTilesCallback }), + [setVisibleTilesCallback], + ); // Combine the tile definitions and slots together to create placed tiles const placedTiles = useMemo(() => { @@ -342,9 +346,11 @@ export function Grid< ); useEffect(() => { - for (const tile of placedTiles) - tile.setVisible(tile.y + tile.height <= visibleHeight); - }, [placedTiles, visibleHeight]); + visibleTilesCallback?.( + placedTiles.filter((tile) => tile.y + tile.height <= visibleHeight) + .length, + ); + }, [placedTiles, visibleTilesCallback, visibleHeight]); // Drag state is stored in a ref rather than component state, because we use // react-spring's imperative API during gestures to improve responsiveness diff --git a/src/grid/GridLayout.tsx b/src/grid/GridLayout.tsx index 45aecd85..f4a29379 100644 --- a/src/grid/GridLayout.tsx +++ b/src/grid/GridLayout.tsx @@ -13,7 +13,7 @@ import { type GridLayout as GridLayoutModel } from "../state/CallViewModel"; import styles from "./GridLayout.module.css"; import { useInitial } from "../useInitial"; import { type CallLayout, arrangeTiles } from "./CallLayout"; -import { type DragCallback, useUpdateLayout } from "./Grid"; +import { type DragCallback, useUpdateLayout, useVisibleTiles } from "./Grid"; interface GridCSSProperties extends CSSProperties { "--gap": string; @@ -73,6 +73,7 @@ export const makeGridLayout: CallLayout = ({ // The scrolling part of the layout is where all the grid tiles live scrolling: forwardRef(function GridLayout({ model, Slot }, ref) { useUpdateLayout(); + useVisibleTiles(model.setVisibleTiles); const { width, height: minHeight } = useObservableEagerState(minBounds); const { gap, tileWidth, tileHeight } = useMemo( () => arrangeTiles(width, minHeight, model.grid.length), @@ -93,13 +94,7 @@ export const makeGridLayout: CallLayout = ({ } > {model.grid.map((m) => ( - + ))}
); diff --git a/src/grid/OneOnOneLayout.tsx b/src/grid/OneOnOneLayout.tsx index e841a686..fb0af714 100644 --- a/src/grid/OneOnOneLayout.tsx +++ b/src/grid/OneOnOneLayout.tsx @@ -52,7 +52,6 @@ export const makeOneOnOneLayout: CallLayout = ({ @@ -61,7 +60,6 @@ export const makeOneOnOneLayout: CallLayout = ({ id={model.local.id} model={model.local} onDrag={onDragLocalTile} - onVisibilityChange={model.local.setVisible} data-block-alignment={pipAlignmentValue.block} data-inline-alignment={pipAlignmentValue.inline} /> diff --git a/src/grid/SpotlightExpandedLayout.tsx b/src/grid/SpotlightExpandedLayout.tsx index 371930ce..d4ce9af3 100644 --- a/src/grid/SpotlightExpandedLayout.tsx +++ b/src/grid/SpotlightExpandedLayout.tsx @@ -63,7 +63,6 @@ export const makeSpotlightExpandedLayout: CallLayout< id={model.pip.id} model={model.pip} onDrag={onDragPip} - onVisibilityChange={model.pip.setVisible} data-block-alignment={pipAlignmentValue.block} data-inline-alignment={pipAlignmentValue.inline} /> diff --git a/src/grid/SpotlightLandscapeLayout.tsx b/src/grid/SpotlightLandscapeLayout.tsx index 8596aec8..3b80a166 100644 --- a/src/grid/SpotlightLandscapeLayout.tsx +++ b/src/grid/SpotlightLandscapeLayout.tsx @@ -12,7 +12,7 @@ import classNames from "classnames"; import { type CallLayout } from "./CallLayout"; import { type SpotlightLandscapeLayout as SpotlightLandscapeLayoutModel } from "../state/CallViewModel"; import styles from "./SpotlightLandscapeLayout.module.css"; -import { useUpdateLayout } from "./Grid"; +import { useUpdateLayout, useVisibleTiles } from "./Grid"; /** * An implementation of the "spotlight landscape" layout, in which the spotlight @@ -50,6 +50,7 @@ export const makeSpotlightLandscapeLayout: CallLayout< ref, ) { useUpdateLayout(); + useVisibleTiles(model.setVisibleTiles); useObservableEagerState(minBounds); const withIndicators = useObservableEagerState(model.spotlight.media).length > 1; @@ -63,13 +64,7 @@ export const makeSpotlightLandscapeLayout: CallLayout< />
{model.grid.map((m) => ( - + ))}
diff --git a/src/grid/SpotlightPortraitLayout.tsx b/src/grid/SpotlightPortraitLayout.tsx index beeca3e6..6af0fa39 100644 --- a/src/grid/SpotlightPortraitLayout.tsx +++ b/src/grid/SpotlightPortraitLayout.tsx @@ -12,7 +12,7 @@ import classNames from "classnames"; import { type CallLayout, arrangeTiles } from "./CallLayout"; import { type SpotlightPortraitLayout as SpotlightPortraitLayoutModel } from "../state/CallViewModel"; import styles from "./SpotlightPortraitLayout.module.css"; -import { useUpdateLayout } from "./Grid"; +import { useUpdateLayout, useVisibleTiles } from "./Grid"; interface GridCSSProperties extends CSSProperties { "--grid-gap": string; @@ -54,6 +54,7 @@ export const makeSpotlightPortraitLayout: CallLayout< ref, ) { useUpdateLayout(); + useVisibleTiles(model.setVisibleTiles); const { width } = useObservableEagerState(minBounds); const { gap, tileWidth, tileHeight } = arrangeTiles( width, @@ -84,13 +85,7 @@ export const makeSpotlightPortraitLayout: CallLayout< />
{model.grid.map((m) => ( - + ))}
diff --git a/src/state/CallViewModel.test.ts b/src/state/CallViewModel.test.ts index ad7e8702..dca21de2 100644 --- a/src/state/CallViewModel.test.ts +++ b/src/state/CallViewModel.test.ts @@ -402,10 +402,7 @@ test("participants stay in the same order unless to appear/disappear", () => { // We imagine that only three tiles (the first three) will be visible // on screen at a time vm.layout.subscribe((layout) => { - if (layout.type === "grid") { - for (let i = 0; i < layout.grid.length; i++) - layout.grid[i].setVisible(i < 3); - } + if (layout.type === "grid") layout.setVisibleTiles(3); }); }, }); diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 72712aa9..108a44c9 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -143,18 +143,21 @@ export interface GridLayout { type: "grid"; spotlight?: SpotlightTileViewModel; grid: GridTileViewModel[]; + setVisibleTiles: (value: number) => void; } export interface SpotlightLandscapeLayout { type: "spotlight-landscape"; spotlight: SpotlightTileViewModel; grid: GridTileViewModel[]; + setVisibleTiles: (value: number) => void; } export interface SpotlightPortraitLayout { type: "spotlight-portrait"; spotlight: SpotlightTileViewModel; grid: GridTileViewModel[]; + setVisibleTiles: (value: number) => void; } export interface SpotlightExpandedLayout { @@ -223,7 +226,6 @@ enum SortingBin { interface LayoutScanState { layout: Layout | null; tiles: TileStore; - visibleTiles: Set; } class UserMedia { @@ -891,62 +893,53 @@ export class CallViewModel extends ViewModel { this.scope.state(), ); + // 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 + // are visible, and loop the data back into the layouts with a Subject. + private readonly visibleTiles = new Subject(); + private readonly setVisibleTiles = (value: number): void => + this.visibleTiles.next(value); + public readonly layoutInternals: Observable< LayoutScanState & { layout: Layout } - > = this.layoutMedia.pipe( - // Each layout will produce a set of tiles, and these tiles have an - // observable indicating whether they're visible. We loop this information - // back into the layout process by using switchScan. - switchScan< - LayoutMedia, - LayoutScanState, - Observable + > = combineLatest([ + this.layoutMedia, + this.visibleTiles.pipe(startWith(0), distinctUntilChanged()), + ]).pipe( + scan< + [LayoutMedia, number], + LayoutScanState & { layout: Layout }, + LayoutScanState >( - ({ tiles: prevTiles, visibleTiles }, media) => { + ({ tiles: prevTiles }, [media, visibleTiles]) => { let layout: Layout; let newTiles: TileStore; switch (media.type) { case "grid": case "spotlight-landscape": case "spotlight-portrait": - [layout, newTiles] = gridLikeLayout(media, visibleTiles, prevTiles); - break; - case "spotlight-expanded": - [layout, newTiles] = spotlightExpandedLayout( + [layout, newTiles] = gridLikeLayout( media, visibleTiles, + this.setVisibleTiles, prevTiles, ); break; + case "spotlight-expanded": + [layout, newTiles] = spotlightExpandedLayout(media, prevTiles); + break; case "one-on-one": - [layout, newTiles] = oneOnOneLayout(media, visibleTiles, prevTiles); + [layout, newTiles] = oneOnOneLayout(media, prevTiles); break; case "pip": - [layout, newTiles] = pipLayout(media, visibleTiles, prevTiles); + [layout, newTiles] = pipLayout(media, prevTiles); break; } - // Take all of the 'visible' observables and combine them into one big - // observable array - const visibilities = - newTiles.gridTiles.length === 0 - ? of([]) - : combineLatest(newTiles.gridTiles.map((tile) => tile.visible)); - return visibilities.pipe( - map((visibilities) => ({ - layout: layout, - tiles: newTiles, - visibleTiles: new Set( - newTiles.gridTiles.filter((_tile, i) => visibilities[i]), - ), - })), - ); - }, - { - layout: null, - tiles: TileStore.empty(), - visibleTiles: new Set(), + return { layout, tiles: newTiles }; }, + { layout: null, tiles: TileStore.empty() }, ), this.scope.state(), ); diff --git a/src/state/GridLikeLayout.ts b/src/state/GridLikeLayout.ts index b846939e..e5a31cf6 100644 --- a/src/state/GridLikeLayout.ts +++ b/src/state/GridLikeLayout.ts @@ -7,7 +7,6 @@ Please see LICENSE in the repository root for full details. import { type Layout, type LayoutMedia } from "./CallViewModel"; import { type TileStore } from "./TileStore"; -import { type GridTileViewModel } from "./TileViewModel"; export type GridLikeLayoutType = | "grid" @@ -20,7 +19,8 @@ export type GridLikeLayoutType = */ export function gridLikeLayout( media: LayoutMedia & { type: GridLikeLayoutType }, - visibleTiles: Set, + visibleTiles: number, + setVisibleTiles: (value: number) => void, prevTiles: TileStore, ): [Layout & { type: GridLikeLayoutType }, TileStore] { const update = prevTiles.from(visibleTiles); @@ -37,6 +37,7 @@ export function gridLikeLayout( type: media.type, spotlight: tiles.spotlightTile, grid: tiles.gridTiles, + setVisibleTiles, } as Layout & { type: GridLikeLayoutType }, tiles, ]; diff --git a/src/state/OneOnOneLayout.ts b/src/state/OneOnOneLayout.ts index f078b7e3..2a0e7ff5 100644 --- a/src/state/OneOnOneLayout.ts +++ b/src/state/OneOnOneLayout.ts @@ -7,17 +7,15 @@ Please see LICENSE in the repository root for full details. import { type OneOnOneLayout, type OneOnOneLayoutMedia } from "./CallViewModel"; import { type TileStore } from "./TileStore"; -import { type GridTileViewModel } from "./TileViewModel"; /** * Produces a one-on-one layout with the given media. */ export function oneOnOneLayout( media: OneOnOneLayoutMedia, - visibleTiles: Set, prevTiles: TileStore, ): [OneOnOneLayout, TileStore] { - const update = prevTiles.from(visibleTiles); + const update = prevTiles.from(2); update.registerGridTile(media.local); update.registerGridTile(media.remote); const tiles = update.build(); diff --git a/src/state/PipLayout.ts b/src/state/PipLayout.ts index 79187752..ad56cdd5 100644 --- a/src/state/PipLayout.ts +++ b/src/state/PipLayout.ts @@ -7,17 +7,15 @@ Please see LICENSE in the repository root for full details. import { type PipLayout, type PipLayoutMedia } from "./CallViewModel"; import { type TileStore } from "./TileStore"; -import { type GridTileViewModel } from "./TileViewModel"; /** * Produces a picture-in-picture layout with the given media. */ export function pipLayout( media: PipLayoutMedia, - visibleTiles: Set, prevTiles: TileStore, ): [PipLayout, TileStore] { - const update = prevTiles.from(visibleTiles); + const update = prevTiles.from(0); update.registerSpotlight(media.spotlight, true); const tiles = update.build(); return [ diff --git a/src/state/SpotlightExpandedLayout.ts b/src/state/SpotlightExpandedLayout.ts index 56b1d01d..c14b24a7 100644 --- a/src/state/SpotlightExpandedLayout.ts +++ b/src/state/SpotlightExpandedLayout.ts @@ -10,17 +10,15 @@ import { type SpotlightExpandedLayoutMedia, } from "./CallViewModel"; import { type TileStore } from "./TileStore"; -import { type GridTileViewModel } from "./TileViewModel"; /** * Produces an expanded spotlight layout with the given media. */ export function spotlightExpandedLayout( media: SpotlightExpandedLayoutMedia, - visibleTiles: Set, prevTiles: TileStore, ): [SpotlightExpandedLayout, TileStore] { - const update = prevTiles.from(visibleTiles); + const update = prevTiles.from(1); update.registerSpotlight(media.spotlight, true); if (media.pip !== undefined) update.registerGridTile(media.pip); const tiles = update.build(); diff --git a/src/state/TileStore.ts b/src/state/TileStore.ts index 2464d9eb..cd269944 100644 --- a/src/state/TileStore.ts +++ b/src/state/TileStore.ts @@ -101,7 +101,7 @@ export class TileStore { * Creates a builder which can be used to update the collection, passing * ownership of the tiles to the updated collection. */ - public from(visibleTiles: Set): TileStoreBuilder { + public from(visibleTiles: number): TileStoreBuilder { return new TileStoreBuilder( this.spotlight, this.grid, @@ -146,7 +146,7 @@ export class TileStoreBuilder { spotlight: SpotlightTileData | null, grid: GridTileData[], ) => TileStore, - private readonly visibleTiles: Set, + private readonly visibleTiles: number, /** * A number incremented on each update, just for debugging purposes. */ @@ -204,10 +204,8 @@ export class TileStoreBuilder { const prev = this.prevGridByMedia.get(this.spotlight.media[0]); if (prev !== undefined) { const [entry, prevIndex] = prev; - const previouslyVisible = this.visibleTiles.has(entry.vm); - const nowVisible = this.visibleTiles.has( - this.prevGrid[this.numGridEntries]?.vm, - ); + const previouslyVisible = prevIndex < this.visibleTiles; + const nowVisible = this.numGridEntries < this.visibleTiles; // If it doesn't need to move between the visible/invisible sections of // the grid, then we can keep it where it was and swap the media @@ -236,17 +234,15 @@ export class TileStoreBuilder { const prev = this.prevGridByMedia.get(media); if (prev === undefined) { // Create a new tile - (this.visibleTiles.has(this.prevGrid[this.numGridEntries]?.vm) + (this.numGridEntries < this.visibleTiles ? this.visibleGridEntries : this.invisibleGridEntries ).push(new GridTileData(media)); } else { // Reuse the existing tile const [entry, prevIndex] = prev; - const previouslyVisible = this.visibleTiles.has(entry.vm); - const nowVisible = this.visibleTiles.has( - this.prevGrid[this.numGridEntries]?.vm, - ); + const previouslyVisible = prevIndex < this.visibleTiles; + const nowVisible = this.numGridEntries < this.visibleTiles; // If it doesn't need to move between the visible/invisible sections of // the grid, then we can keep it exactly where it was previously if (previouslyVisible === nowVisible) diff --git a/src/state/TileViewModel.ts b/src/state/TileViewModel.ts index 53bc8648..612d7033 100644 --- a/src/state/TileViewModel.ts +++ b/src/state/TileViewModel.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { BehaviorSubject, type Observable } from "rxjs"; +import { type Observable } from "rxjs"; import { ViewModel } from "./ViewModel"; import { type MediaViewModel, type UserMediaViewModel } from "./MediaViewModel"; @@ -18,14 +18,6 @@ function createId(): string { export class GridTileViewModel extends ViewModel { public readonly id = createId(); - private readonly visible_ = new BehaviorSubject(false); - /** - * Whether the tile is visible within the current viewport. - */ - public readonly visible: Observable = this.visible_; - - public setVisible = (value: boolean): void => this.visible_.next(value); - public constructor(public readonly media: Observable) { super(); } From 53565ddb7677222132ac972e3c5f299a58d05be8 Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 12 Dec 2024 19:16:01 -0500 Subject: [PATCH 45/72] Test that participants adjust order when screen size changes --- src/state/CallViewModel.test.ts | 62 +++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/src/state/CallViewModel.test.ts b/src/state/CallViewModel.test.ts index dca21de2..0e45faa1 100644 --- a/src/state/CallViewModel.test.ts +++ b/src/state/CallViewModel.test.ts @@ -376,16 +376,16 @@ test("screen sharing activates spotlight layout", () => { test("participants stay in the same order unless to appear/disappear", () => { withTestScheduler(({ hot, schedule, expectObservable }) => { - const modeInputMarbles = " a"; + const visibilityInputMarbles = "a"; // First Bob speaks, then Dave, then Alice - const aSpeakingInputMarbles = "n- 1998ms - 1999ms y"; - const bSpeakingInputMarbles = "ny 1998ms n 1999ms -"; - const dSpeakingInputMarbles = "n- 1998ms y 1999ms n"; + const aSpeakingInputMarbles = " n- 1998ms - 1999ms y"; + const bSpeakingInputMarbles = " ny 1998ms n 1999ms -"; + const dSpeakingInputMarbles = " n- 1998ms y 1999ms n"; // Nothing should change when Bob speaks, because Bob is already on screen. // When Dave speaks he should switch with Alice because she's the one who // hasn't spoken at all. Then when Alice speaks, she should return to her // place at the top. - const expectedLayoutMarbles = "a 1999ms b 1999ms a 57999ms c 1999ms a"; + const expectedLayoutMarbles = " a 1999ms b 1999ms a 57999ms c 1999ms a"; withCallViewModel( of([aliceParticipant, bobParticipant, daveParticipant]), @@ -397,7 +397,7 @@ test("participants stay in the same order unless to appear/disappear", () => { [daveParticipant, hot(dSpeakingInputMarbles, { y: true, n: false })], ]), (vm) => { - schedule(modeInputMarbles, { + schedule(visibilityInputMarbles, { a: () => { // We imagine that only three tiles (the first three) will be visible // on screen at a time @@ -432,6 +432,56 @@ test("participants stay in the same order unless to appear/disappear", () => { }); }); +test("participants adjust order when space becomes constrained", () => { + withTestScheduler(({ hot, schedule, expectObservable }) => { + // Start with all tiles on screen then shrink to 3 + const visibilityInputMarbles = "a-b"; + // Bob and Dave speak + const bSpeakingInputMarbles = " ny"; + const dSpeakingInputMarbles = " ny"; + // Nothing should change when Bob or Dave initially speak, because they are + // on screen. When the screen becomes smaller Alice should move off screen + // to make way for the speakers (specifically, she should swap with Dave). + const expectedLayoutMarbles = " a-b"; + + withCallViewModel( + of([aliceParticipant, bobParticipant, daveParticipant]), + of([aliceRtcMember, bobRtcMember, daveRtcMember]), + of(ConnectionState.Connected), + new Map([ + [bobParticipant, hot(bSpeakingInputMarbles, { y: true, n: false })], + [daveParticipant, hot(dSpeakingInputMarbles, { y: true, n: false })], + ]), + (vm) => { + let setVisibleTiles: ((value: number) => void) | null = null; + vm.layout.subscribe((layout) => { + if (layout.type === "grid") setVisibleTiles = layout.setVisibleTiles; + }); + schedule(visibilityInputMarbles, { + a: () => setVisibleTiles!(Infinity), + b: () => setVisibleTiles!(3), + }); + + expectObservable(summarizeLayout(vm.layout)).toBe( + expectedLayoutMarbles, + { + a: { + type: "grid", + spotlight: undefined, + grid: ["local:0", `${aliceId}:0`, `${bobId}:0`, `${daveId}:0`], + }, + b: { + type: "grid", + spotlight: undefined, + grid: ["local:0", `${daveId}:0`, `${bobId}:0`, `${aliceId}:0`], + }, + }, + ); + }, + ); + }); +}); + test("spotlight speakers swap places", () => { withTestScheduler(({ hot, schedule, expectObservable }) => { // Go immediately into spotlight mode for the test From b993c5eb12253f617c3065c8f0babd5adec3ee13 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 13 Dec 2024 03:23:20 +0100 Subject: [PATCH 46/72] fix linter --- src/livekit/useLiveKit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index 1bfe01d1..3cae96fd 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { ConnectionState, - type E2EEOptions, + type E2EEManagerOptions, ExternalE2EEKeyProvider, Room, type RoomOptions, @@ -45,7 +45,7 @@ export function useLiveKit( sfuConfig: SFUConfig | undefined, e2eeSystem: EncryptionSystem, ): UseLivekitResult { - const e2eeOptions = useMemo((): E2EEOptions | undefined => { + const e2eeOptions = useMemo((): E2EEManagerOptions | undefined => { if (e2eeSystem.kind === E2eeType.NONE) return undefined; if (e2eeSystem.kind === E2eeType.PER_PARTICIPANT) { From 7807c44fdcb7df8ed688ccd39a09da2cb0273eee Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 13 Dec 2024 10:09:27 +0000 Subject: [PATCH 47/72] Clean up useMuteStates test (#2891) We don't need to be mocking React for this test. --- src/room/MuteStates.test.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/room/MuteStates.test.tsx b/src/room/MuteStates.test.tsx index 77ab8ace..0db95a53 100644 --- a/src/room/MuteStates.test.tsx +++ b/src/room/MuteStates.test.tsx @@ -6,8 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; -import React, { type ReactNode } from "react"; -import { beforeEach } from "vitest"; +import { type ReactNode } from "react"; import { render, screen } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; @@ -92,16 +91,12 @@ function mockMediaDevices( } describe("useMuteStates", () => { - beforeEach(() => { - vi.spyOn(React, "useContext").mockReturnValue({}); - }); - afterEach(() => { - vi.restoreAllMocks(); + vi.clearAllMocks(); }); afterAll(() => { - vi.clearAllMocks(); + vi.resetAllMocks(); }); it("disabled when no input devices", () => { From e84ecc68b3ee4e3dd9d090f8f4c96051aa1c095d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 13 Dec 2024 13:23:32 +0000 Subject: [PATCH 48/72] Refactor preferences tab strings for consistency (#2888) * Refactor preferences tab strings for consistency * Lint --- locales/de/app.json | 8 ++++---- locales/en/app.json | 8 ++++---- locales/ro/app.json | 8 ++++---- src/settings/PreferencesSettingsTab.tsx | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/locales/de/app.json b/locales/de/app.json index 67138ca5..3ba4a36a 100644 --- a/locales/de/app.json +++ b/locales/de/app.json @@ -161,16 +161,16 @@ "more_tab_title": "Mehr", "opt_in_description": "<0><1>Du kannst deine Zustimmung durch Abwählen dieses Kästchens zurückziehen. Falls du dich aktuell in einem Anruf befindest, wird diese Einstellung nach dem Ende des Anrufs wirksam.", "preferences_tab": { + "introduction": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden", "reactions_play_sound_description": "Einen Soundeffekt abspielen, wenn jemand eine Reaktion sendet", "reactions_play_sound_label": "Reaktionstöne abspielen", "reactions_show_description": "Zeige eine Animation, wenn jemand eine Reaktion sendet.", "reactions_show_label": "Reaktionen anzeigen", - "reactions_title": "Reaktionen" + "reactions_title": "Reaktionen", + "show_hand_raised_timer_description": "Einen Timer zur Handmeldung anzeigen", + "show_hand_raised_timer_label": "Dauer der Handmeldung anzeigen" }, - "preferences_tab_body": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden", "preferences_tab_h4": "Einstellungen", - "preferences_tab_show_hand_raised_timer_description": "Einen Timer zur Handmeldung anzeigen", - "preferences_tab_show_hand_raised_timer_label": "Dauer der Handmeldung anzeigen", "speaker_device_selection_label": "Lautsprecher" }, "star_rating_input_label_one": "{{count}} Stern", diff --git a/locales/en/app.json b/locales/en/app.json index 0d2b5f15..84038c2b 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -162,14 +162,14 @@ "preferences_tab": { "developer_mode_label": "Developer mode", "developer_mode_label_description": "Enable developer mode and show developer settings tab.", + "introduction": "Here you can configure extra options for an improved experience.", "reactions_play_sound_description": "Play a sound effect when anyone sends a reaction into a call.", "reactions_play_sound_label": "Play reaction sounds", "reactions_show_description": "Show an animation when anyone sends a reaction.", - "reactions_show_label": "Show reactions" + "reactions_show_label": "Show reactions", + "show_hand_raised_timer_description": "Show a timer when a participant raises their hand", + "show_hand_raised_timer_label": "Show hand raise duration" }, - "preferences_tab_body": "Here you can configure extra options for an improved experience.", - "preferences_tab_show_hand_raised_timer_description": "Show a timer when a participant raises their hand", - "preferences_tab_show_hand_raised_timer_label": "Show hand raise duration", "speaker_device_selection_label": "Speaker" }, "star_rating_input_label_one": "{{count}} star", diff --git a/locales/ro/app.json b/locales/ro/app.json index ab55c511..6d0de16f 100644 --- a/locales/ro/app.json +++ b/locales/ro/app.json @@ -160,16 +160,16 @@ "more_tab_title": "Mai mult", "opt_in_description": "<0><1>Puteți retrage consimțământul debifând această casetă. Dacă sunteți în prezent la un apel, această setare va intra în vigoare la sfârșitul apelului.", "preferences_tab": { + "introduction": "Aici puteți configura opțiuni suplimentare pentru o experiență îmbunătățită", "reactions_play_sound_description": "Redați un efect sonor atunci când cineva trimite o reacție la un apel.", "reactions_play_sound_label": "Redați sunete de reacție", "reactions_show_description": "Afișați o animație atunci când cineva trimite o reacție.", "reactions_show_label": "Afișați reacțiile", - "reactions_title": "Reacții" + "reactions_title": "Reacții", + "show_hand_raised_timer_description": "Afișați un cronometru atunci când un participant ridică mâna", + "show_hand_raised_timer_label": "Afișați durata ridicării mâinii" }, - "preferences_tab_body": "Aici puteți configura opțiuni suplimentare pentru o experiență îmbunătățită", "preferences_tab_h4": "preferinte", - "preferences_tab_show_hand_raised_timer_description": "Afișați un cronometru atunci când un participant ridică mâna", - "preferences_tab_show_hand_raised_timer_label": "Afișați durata ridicării mâinii", "speaker_device_selection_label": "vorbitor" }, "start_new_call": "Începe un nou apel", diff --git a/src/settings/PreferencesSettingsTab.tsx b/src/settings/PreferencesSettingsTab.tsx index fd4be1ae..72d2d919 100644 --- a/src/settings/PreferencesSettingsTab.tsx +++ b/src/settings/PreferencesSettingsTab.tsx @@ -41,13 +41,13 @@ export const PreferencesSettingsTab: FC = () => { return (
- {t("settings.preferences_tab_body")} + {t("settings.preferences_tab.introduction")} Date: Fri, 13 Dec 2024 14:12:29 +0000 Subject: [PATCH 49/72] Bump matrix-js-sdk (#2897) So that we get https://github.com/matrix-org/matrix-js-sdk/pull/4575 Full diff: https://github.com/matrix-org/matrix-js-sdk/compare/edac6a9983bd604c17535a9ae673dc979c7b61c4...d1de32ea2773df4c6f8a956678bbd19b6d022475 --- yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 75c2ef72..d607f394 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1961,10 +1961,10 @@ dependencies: "@bufbuild/protobuf" "^1.10.0" -"@matrix-org/matrix-sdk-crypto-wasm@^9.0.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-9.1.0.tgz#f889653eb4fafaad2a963654d586bd34de62acd5" - integrity sha512-CtPoNcoRW6ehwxpRQAksG3tR+NJ7k4DV02nMFYTDwQtie1V4R8OTY77BjEIs97NOblhtS26jU8m1lWsOBEz0Og== +"@matrix-org/matrix-sdk-crypto-wasm@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-11.0.0.tgz#c49a1a0d1e367d3c00a2144a4ab23caee0b1eec2" + integrity sha512-a7NUH8Kjc8hwzNCPpkOGXoceFqWJiWvA8OskXeDrKyODJuDz4yKrZ/nvgaVRfQe45Ab5UC1ZXYqaME+ChlJuqg== "@matrix-org/olm@3.2.15": version "3.2.15" @@ -6494,11 +6494,11 @@ matrix-events-sdk@0.0.1: integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== matrix-js-sdk@matrix-org/matrix-js-sdk#develop: - version "34.12.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/edac6a9983bd604c17535a9ae673dc979c7b61c4" + version "34.13.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d1de32ea2773df4c6f8a956678bbd19b6d022475" dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" + "@matrix-org/matrix-sdk-crypto-wasm" "^11.0.0" "@matrix-org/olm" "3.2.15" another-json "^0.2.0" bs58 "^6.0.0" From 31be6cc804c0a9654caaafed73d5d6a3757fa736 Mon Sep 17 00:00:00 2001 From: robintown <48614497+robintown@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:28:28 +0000 Subject: [PATCH 50/72] Translations updates --- locales/bg/app.json | 1 - locales/cs/app.json | 3 --- locales/de/app.json | 11 ++--------- locales/el/app.json | 3 --- locales/es/app.json | 3 --- locales/et/app.json | 3 --- locales/fa/app.json | 1 - locales/fr/app.json | 3 --- locales/id/app.json | 3 --- locales/it/app.json | 3 --- locales/lv/app.json | 3 --- locales/pl/app.json | 3 --- locales/ro/app.json | 10 +--------- locales/ru/app.json | 3 --- locales/sk/app.json | 3 --- locales/tr/app.json | 3 +-- locales/uk/app.json | 3 --- locales/vi/app.json | 1 - locales/zh-Hans/app.json | 3 --- locales/zh-Hant/app.json | 3 --- 20 files changed, 4 insertions(+), 65 deletions(-) diff --git a/locales/bg/app.json b/locales/bg/app.json index 4d51f573..eb9e5704 100644 --- a/locales/bg/app.json +++ b/locales/bg/app.json @@ -62,7 +62,6 @@ "developer_tab_title": "Разработчик", "feedback_tab_h4": "Изпрати обратна връзка", "feedback_tab_send_logs_label": "Включи debug логове", - "more_tab_title": "Още", "speaker_device_selection_label": "Говорител" }, "unauthenticated_view_body": "Все още не сте регистрирани? <2>Създайте акаунт", diff --git a/locales/cs/app.json b/locales/cs/app.json index 8089af9a..b9793e1f 100644 --- a/locales/cs/app.json +++ b/locales/cs/app.json @@ -60,12 +60,9 @@ "return_home_button": "Vrátit se na domácí obrazovku", "screenshare_button_label": "Sdílet obrazovku", "settings": { - "developer_settings_label": "Vývojářské nastavení", - "developer_settings_label_description": "Zobrazit vývojářské nastavení.", "developer_tab_title": "Vývojář", "feedback_tab_h4": "Dát feedback", "feedback_tab_send_logs_label": "Zahrnout ladící záznamy", - "more_tab_title": "Více", "speaker_device_selection_label": "Reproduktor" }, "unauthenticated_view_body": "Nejste registrovaní? <2>Vytvořit účet", diff --git a/locales/de/app.json b/locales/de/app.json index 3ba4a36a..2c35b341 100644 --- a/locales/de/app.json +++ b/locales/de/app.json @@ -147,10 +147,9 @@ "screenshare_button_label": "Bildschirm teilen", "settings": { "audio_tab": { + "effect_volume_description": "Lautstärke anpassen, mit der Reaktionen und Handmeldungen abgespielt werden", "effect_volume_label": "Lautstärke der Soundeffekte" }, - "developer_settings_label": "Entwicklereinstellungen", - "developer_settings_label_description": "Zeige die Entwicklereinstellungen im Einstellungsfenster.", "developer_tab_title": "Entwickler", "feedback_tab_body": "Falls du auf Probleme stößt oder einfach nur eine Rückmeldung geben möchtest, sende uns bitte eine kurze Beschreibung.", "feedback_tab_description_label": "Deine Rückmeldung", @@ -158,19 +157,13 @@ "feedback_tab_send_logs_label": "Debug-Protokolle anhängen", "feedback_tab_thank_you": "Danke, wir haben deine Rückmeldung erhalten!", "feedback_tab_title": "Rückmeldung", - "more_tab_title": "Mehr", "opt_in_description": "<0><1>Du kannst deine Zustimmung durch Abwählen dieses Kästchens zurückziehen. Falls du dich aktuell in einem Anruf befindest, wird diese Einstellung nach dem Ende des Anrufs wirksam.", "preferences_tab": { - "introduction": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden", "reactions_play_sound_description": "Einen Soundeffekt abspielen, wenn jemand eine Reaktion sendet", "reactions_play_sound_label": "Reaktionstöne abspielen", "reactions_show_description": "Zeige eine Animation, wenn jemand eine Reaktion sendet.", - "reactions_show_label": "Reaktionen anzeigen", - "reactions_title": "Reaktionen", - "show_hand_raised_timer_description": "Einen Timer zur Handmeldung anzeigen", - "show_hand_raised_timer_label": "Dauer der Handmeldung anzeigen" + "reactions_show_label": "Reaktionen anzeigen" }, - "preferences_tab_h4": "Einstellungen", "speaker_device_selection_label": "Lautsprecher" }, "star_rating_input_label_one": "{{count}} Stern", diff --git a/locales/el/app.json b/locales/el/app.json index c4b7a7a9..10b9396d 100644 --- a/locales/el/app.json +++ b/locales/el/app.json @@ -67,8 +67,6 @@ "return_home_button": "Επιστροφή στην αρχική οθόνη", "screenshare_button_label": "Κοινή χρήση οθόνης", "settings": { - "developer_settings_label": "Ρυθμίσεις προγραμματιστή", - "developer_settings_label_description": "Εμφάνιση ρυθμίσεων προγραμματιστή στο παράθυρο ρυθμίσεων.", "developer_tab_title": "Προγραμματιστής", "feedback_tab_body": "Εάν αντιμετωπίζετε προβλήματα ή απλά θέλετε να μας δώσετε κάποια σχόλια, παρακαλούμε στείλτε μας μια σύντομη περιγραφή παρακάτω.", "feedback_tab_description_label": "Τα σχόλιά σας", @@ -76,7 +74,6 @@ "feedback_tab_send_logs_label": "Να συμπεριληφθούν αρχεία καταγραφής", "feedback_tab_thank_you": "Ευχαριστούμε, λάβαμε τα σχόλιά σας!", "feedback_tab_title": "Ανατροφοδότηση", - "more_tab_title": "Περισσότερα", "opt_in_description": "<0><1>Μπορείτε να ανακαλέσετε τη συγκατάθεσή σας αποεπιλέγοντας αυτό το πλαίσιο. Εάν βρίσκεστε σε κλήση, η ρύθμιση αυτή θα τεθεί σε ισχύ στο τέλος της.", "speaker_device_selection_label": "Ηχείο" }, diff --git a/locales/es/app.json b/locales/es/app.json index c9e9ee1a..96f6710c 100644 --- a/locales/es/app.json +++ b/locales/es/app.json @@ -67,8 +67,6 @@ "room_auth_view_eula_caption": "Al hacer clic en \"Unirse a la llamada ahora\", aceptas nuestro <2>Contrato de Licencia de Usuario Final (CLUF)", "screenshare_button_label": "Compartir pantalla", "settings": { - "developer_settings_label": "Ajustes de desarrollador", - "developer_settings_label_description": "Muestra los ajustes de desarrollador en la ventana de ajustes.", "developer_tab_title": "Desarrollador", "feedback_tab_body": "Si tienes algún problema o simplemente quieres darnos tu opinión, por favor envíanos una breve descripción.", "feedback_tab_description_label": "Tus comentarios", @@ -76,7 +74,6 @@ "feedback_tab_send_logs_label": "Incluir registros de depuración", "feedback_tab_thank_you": "¡Gracias, hemos recibido tus comentarios!", "feedback_tab_title": "Danos tu opinión", - "more_tab_title": "Más", "opt_in_description": "<0><1>Puedes retirar tu consentimiento desmarcando esta casilla. Si estás en una llamada, este ajuste se aplicará al final de esta.", "speaker_device_selection_label": "Altavoz" }, diff --git a/locales/et/app.json b/locales/et/app.json index bdce05f6..ccd1c699 100644 --- a/locales/et/app.json +++ b/locales/et/app.json @@ -97,8 +97,6 @@ "room_auth_view_eula_caption": "Klõpsides „Liitu kõnega kohe“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)", "screenshare_button_label": "Jaga ekraani", "settings": { - "developer_settings_label": "Arendaja seadistused", - "developer_settings_label_description": "Näita seadistuste aknas arendajale vajalikke seadeid.", "developer_tab_title": "Arendaja", "feedback_tab_body": "Kui selle rakenduse kasutamisel tekib sul probleeme või lihtsalt soovid oma arvamust avaldada, siis palun täida alljärgnev lühike kirjeldus.", "feedback_tab_description_label": "Sinu tagasiside", @@ -106,7 +104,6 @@ "feedback_tab_send_logs_label": "Lisa veatuvastuslogid", "feedback_tab_thank_you": "Tänud, me oleme sinu tagasiside kätte saanud!", "feedback_tab_title": "Tagasiside", - "more_tab_title": "Rohkem", "opt_in_description": "<0><1>Sa võid selle valiku eelmaldamisega alati oma nõusoleku tagasi võtta. Kui sul parasjagu on kõne pooleli, siis seadistuste muudatus jõustub pärast kõne lõppu.", "speaker_device_selection_label": "Kõlar" }, diff --git a/locales/fa/app.json b/locales/fa/app.json index 7fdf98a2..125ec785 100644 --- a/locales/fa/app.json +++ b/locales/fa/app.json @@ -64,7 +64,6 @@ "developer_tab_title": "توسعه دهنده", "feedback_tab_h4": "بازخورد ارائه دهید", "feedback_tab_send_logs_label": "شامل لاگ‌های عیب‌یابی", - "more_tab_title": "بیشتر", "speaker_device_selection_label": "بلندگو" }, "unauthenticated_view_body": "هنوز ثبت‌نام نکرده‌اید؟ <2>ساخت حساب کاربری", diff --git a/locales/fr/app.json b/locales/fr/app.json index 4cb2b9e4..f465244d 100644 --- a/locales/fr/app.json +++ b/locales/fr/app.json @@ -95,8 +95,6 @@ "room_auth_view_eula_caption": "En cliquant sur « Rejoindre l’appel maintenant », vous acceptez notre <2>Contrat de Licence Utilisateur Final (CLUF)", "screenshare_button_label": "Partage d’écran", "settings": { - "developer_settings_label": "Paramètres développeurs", - "developer_settings_label_description": "Affiche les paramètres développeurs dans la fenêtre des paramètres.", "developer_tab_title": "Développeur", "feedback_tab_body": "Si vous rencontrez des problèmes, ou vous voulez simplement faire un commentaire, faites-en une courte description ci-dessous.", "feedback_tab_description_label": "Votre commentaire", @@ -104,7 +102,6 @@ "feedback_tab_send_logs_label": "Inclure les journaux de débogage", "feedback_tab_thank_you": "Merci, nous avons reçu vos commentaires !", "feedback_tab_title": "Commentaires", - "more_tab_title": "Plus", "opt_in_description": "<0><1>Vous pouvez retirer votre consentement en décochant cette case. Si vous êtes actuellement en communication, ce paramètre prendra effet à la fin de l’appel.", "speaker_device_selection_label": "Intervenant" }, diff --git a/locales/id/app.json b/locales/id/app.json index c1c7fae3..c479d604 100644 --- a/locales/id/app.json +++ b/locales/id/app.json @@ -96,8 +96,6 @@ "room_auth_view_eula_caption": "Dengan mengeklik \"Bergabung ke panggilan sekarang\", Anda menyetujui <2>Perjanjian Lisensi Pengguna Akhir (EULA) kami", "screenshare_button_label": "Bagikan layar", "settings": { - "developer_settings_label": "Pengaturan Pengembang", - "developer_settings_label_description": "Ekspos pengaturan pengembang dalam jendela pengaturan.", "developer_tab_title": "Pengembang", "feedback_tab_body": "Jika Anda mengalami masalah atau hanya ingin memberikan masukan, silakan kirimkan kami deskripsi pendek di bawah.", "feedback_tab_description_label": "Masukan Anda", @@ -105,7 +103,6 @@ "feedback_tab_send_logs_label": "Termasuk catatan pengawakutuan", "feedback_tab_thank_you": "Terima kasih, kami telah menerima masukan Anda!", "feedback_tab_title": "Masukan", - "more_tab_title": "Lainnya", "opt_in_description": "<0><1>Anda dapat mengurungkan kembali izin dengan mencentang kotak ini. Jika Anda saat ini dalam panggilan, pengaturan ini akan diterapkan di akhir panggilan.", "speaker_device_selection_label": "Pembicara" }, diff --git a/locales/it/app.json b/locales/it/app.json index a3d1d797..6fe08427 100644 --- a/locales/it/app.json +++ b/locales/it/app.json @@ -94,15 +94,12 @@ "room_auth_view_eula_caption": "Cliccando \"Entra in chiamata ora\", accetti il nostro <2>accordo di licenza con l'utente finale (EULA)", "screenshare_button_label": "Condividi schermo", "settings": { - "developer_settings_label": "Impostazioni per sviluppatori", - "developer_settings_label_description": "Mostra le impostazioni per sviluppatori nella finestra delle impostazioni.", "developer_tab_title": "Sviluppatore", "feedback_tab_body": "Se stai riscontrando problemi o semplicemente vuoi dare un'opinione, inviaci una breve descrizione qua sotto.", "feedback_tab_description_label": "Il tuo commento", "feedback_tab_h4": "Invia commento", "feedback_tab_send_logs_label": "Includi registri di debug", "feedback_tab_thank_you": "Grazie, abbiamo ricevuto il tuo commento!", - "more_tab_title": "Altro", "opt_in_description": "<0><1>Puoi revocare il consenso deselezionando questa casella. Se attualmente sei in una chiamata, avrà effetto al termine di essa.", "speaker_device_selection_label": "Altoparlante" }, diff --git a/locales/lv/app.json b/locales/lv/app.json index a92d4bf2..ee48986f 100644 --- a/locales/lv/app.json +++ b/locales/lv/app.json @@ -75,8 +75,6 @@ "room_auth_view_eula_caption": "Klikšķināšana uz \"Pievienoties zvanam tagad\" apliecina piekrišanu mūsu <2>galalietotāja licencēšanas nolīgumam (GLLN)", "screenshare_button_label": "Kopīgot ekrānu", "settings": { - "developer_settings_label": "Izstrādātāja iestatījumi", - "developer_settings_label_description": "Izstādīt izstrādātāja iestatījumus iestatījumu logā.", "developer_tab_title": "Izstrādātājs", "feedback_tab_body": "Ja tiek piedzīvoti sarežģījumi vai vienkārši ir vēlme sniegt kādu atsauksmi, lūgums zemāk nosūtīt mums īsu aprakstu.", "feedback_tab_description_label": "Tava atsauksme", @@ -84,7 +82,6 @@ "feedback_tab_send_logs_label": "Iekļaut atkļūdošanas žurnāla ierakstus", "feedback_tab_thank_you": "Paldies, mēs saņēmām atsauksmi!", "feedback_tab_title": "Atsauksmes", - "more_tab_title": "Vairāk", "opt_in_description": "<0><1>Savu piekrišanu var atsaukt ar atzīmes noņemšanu no šīs rūtiņas. Ja pašreiz atrodies zvanā, šis iestatījums stāsies spēkā zvana beigās.", "speaker_device_selection_label": "Runātājs" }, diff --git a/locales/pl/app.json b/locales/pl/app.json index 7b27c488..db3986ef 100644 --- a/locales/pl/app.json +++ b/locales/pl/app.json @@ -99,8 +99,6 @@ "room_auth_view_eula_caption": "Klikając \"Dołącz teraz do rozmowy\", zgadzasz się na naszą <2>Umowę licencyjną (EULA)", "screenshare_button_label": "Udostępnij ekran", "settings": { - "developer_settings_label": "Opcje programisty", - "developer_settings_label_description": "Wyświetl opcje programisty w oknie ustawień.", "developer_tab_title": "Programista", "feedback_tab_body": "Jeśli posiadasz problemy lub chciałbyś zgłosić swoją opinię, wyślij nam krótki opis.", "feedback_tab_description_label": "Twoje opinie", @@ -108,7 +106,6 @@ "feedback_tab_send_logs_label": "Dołącz dzienniki debugowania", "feedback_tab_thank_you": "Dziękujemy, otrzymaliśmy Twoją opinię!", "feedback_tab_title": "Opinia użytkownika", - "more_tab_title": "Więcej", "opt_in_description": "<0><1>Możesz wycofać swoją zgodę poprzez odznaczenie tego pola. Jeśli już jesteś w trakcie rozmowy, opcja zostanie zastosowana po jej zakończeniu.", "speaker_device_selection_label": "Głośnik" }, diff --git a/locales/ro/app.json b/locales/ro/app.json index 6d0de16f..0b83b0f3 100644 --- a/locales/ro/app.json +++ b/locales/ro/app.json @@ -148,8 +148,6 @@ "effect_volume_description": "Reglați volumul la care reacționează reacțiile și efectele ridicate de mână", "effect_volume_label": "Volumul efectului sonor" }, - "developer_settings_label": "Setări pentru dezvoltatori", - "developer_settings_label_description": "Expuneți setările dezvoltatorului în fereastra de setări.", "developer_tab_title": "dezvoltator", "feedback_tab_body": "Dacă întâmpinați probleme sau pur și simplu doriți să oferiți feedback, vă rugăm să ne trimiteți o scurtă descriere mai jos.", "feedback_tab_description_label": "Feedback-ul tău", @@ -157,19 +155,13 @@ "feedback_tab_send_logs_label": "Includeți jurnale de depanare", "feedback_tab_thank_you": "Vă mulțumim, am primit feedback-ul dvs.!", "feedback_tab_title": "Feedback", - "more_tab_title": "Mai mult", "opt_in_description": "<0><1>Puteți retrage consimțământul debifând această casetă. Dacă sunteți în prezent la un apel, această setare va intra în vigoare la sfârșitul apelului.", "preferences_tab": { - "introduction": "Aici puteți configura opțiuni suplimentare pentru o experiență îmbunătățită", "reactions_play_sound_description": "Redați un efect sonor atunci când cineva trimite o reacție la un apel.", "reactions_play_sound_label": "Redați sunete de reacție", "reactions_show_description": "Afișați o animație atunci când cineva trimite o reacție.", - "reactions_show_label": "Afișați reacțiile", - "reactions_title": "Reacții", - "show_hand_raised_timer_description": "Afișați un cronometru atunci când un participant ridică mâna", - "show_hand_raised_timer_label": "Afișați durata ridicării mâinii" + "reactions_show_label": "Afișați reacțiile" }, - "preferences_tab_h4": "preferinte", "speaker_device_selection_label": "vorbitor" }, "start_new_call": "Începe un nou apel", diff --git a/locales/ru/app.json b/locales/ru/app.json index 0836b2bb..725fffdd 100644 --- a/locales/ru/app.json +++ b/locales/ru/app.json @@ -69,8 +69,6 @@ "return_home_button": "Вернуться в Начало", "screenshare_button_label": "Поделиться экраном", "settings": { - "developer_settings_label": "Настройки Разработчика", - "developer_settings_label_description": "Раскрыть настройки разработчика в окне настроек.", "developer_tab_title": "Разработчику", "feedback_tab_body": "Если у вас возникли проблемы или вы просто хотите оставить отзыв, отправьте нам краткое описание ниже.", "feedback_tab_description_label": "Ваш отзыв", @@ -78,7 +76,6 @@ "feedback_tab_send_logs_label": "Приложить журнал отладки", "feedback_tab_thank_you": "Спасибо. Мы получили ваш отзыв!", "feedback_tab_title": "Отзыв", - "more_tab_title": "Больше", "opt_in_description": "<0><1>Вы можете отозвать согласие, сняв этот флажок. Если вы в данный момент находитесь в разговоре, эта настройка вступит в силу по окончании разговора.", "speaker_device_selection_label": "Динамик" }, diff --git a/locales/sk/app.json b/locales/sk/app.json index 48d69430..fbf2dc37 100644 --- a/locales/sk/app.json +++ b/locales/sk/app.json @@ -97,8 +97,6 @@ "room_auth_view_eula_caption": "Kliknutím na \"Pripojiť sa k hovoru teraz\" súhlasíte s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)", "screenshare_button_label": "Zdieľať obrazovku", "settings": { - "developer_settings_label": "Nastavenia pre vývojárov", - "developer_settings_label_description": "Zobraziť nastavenia pre vývojárov v okne nastavení.", "developer_tab_title": "Vývojár", "feedback_tab_body": "Ak máte problémy alebo jednoducho chcete poskytnúť spätnú väzbu, pošlite nám krátky popis nižšie.", "feedback_tab_description_label": "Vaša spätná väzba", @@ -106,7 +104,6 @@ "feedback_tab_send_logs_label": "Zahrnúť záznamy o ladení", "feedback_tab_thank_you": "Ďakujeme, dostali sme vašu spätnú väzbu!", "feedback_tab_title": "Spätná väzba", - "more_tab_title": "Viac", "opt_in_description": "<0><1>Súhlas môžete odvolať zrušením označenia tohto políčka. Ak práve prebieha hovor, toto nastavenie nadobudne platnosť po skončení hovoru.", "speaker_device_selection_label": "Reproduktor" }, diff --git a/locales/tr/app.json b/locales/tr/app.json index 8be1769e..d14f6883 100644 --- a/locales/tr/app.json +++ b/locales/tr/app.json @@ -52,8 +52,7 @@ "settings": { "developer_tab_title": "Geliştirici", "feedback_tab_h4": "Geri bildirim ver", - "feedback_tab_send_logs_label": "Hata ayıklama kütüğünü dahil et", - "more_tab_title": "Daha" + "feedback_tab_send_logs_label": "Hata ayıklama kütüğünü dahil et" }, "unauthenticated_view_body": "Kaydolmadınız mı? <2>Hesap açın", "unauthenticated_view_login_button": "Hesabınıza girin" diff --git a/locales/uk/app.json b/locales/uk/app.json index f88d14a8..4faa19df 100644 --- a/locales/uk/app.json +++ b/locales/uk/app.json @@ -99,8 +99,6 @@ "room_auth_view_eula_caption": "Натискаючи \"Приєднатися до виклику зараз\", ви погоджуєтеся з нашою <2>Ліцензійною угодою з кінцевим користувачем (EULA)", "screenshare_button_label": "Поділитися екраном", "settings": { - "developer_settings_label": "Налаштування розробника", - "developer_settings_label_description": "Відкрийте налаштування розробника у вікні налаштувань.", "developer_tab_title": "Розробнику", "feedback_tab_body": "Якщо у вас виникли проблеми або ви просто хочете залишити відгук, надішліть нам короткий опис нижче.", "feedback_tab_description_label": "Ваш відгук", @@ -108,7 +106,6 @@ "feedback_tab_send_logs_label": "Долучити журнали налагодження", "feedback_tab_thank_you": "Дякуємо, ми отримали ваш відгук!", "feedback_tab_title": "Відгук", - "more_tab_title": "Докладніше", "opt_in_description": "<0><1>Ви можете відкликати згоду, прибравши цей прапорець. Якщо ви зараз розмовляєте, це налаштування застосується після завершення виклику.", "speaker_device_selection_label": "Динамік" }, diff --git a/locales/vi/app.json b/locales/vi/app.json index 2e59af69..3bd622ce 100644 --- a/locales/vi/app.json +++ b/locales/vi/app.json @@ -55,7 +55,6 @@ "register_confirm_password_label": "Xác nhận mật khẩu", "screenshare_button_label": "Chia sẻ màn hình", "settings": { - "developer_settings_label": "Cài đặt phát triển", "developer_tab_title": "Nhà phát triển", "feedback_tab_description_label": "Phản hồi của bạn", "feedback_tab_h4": "Gửi phản hồi", diff --git a/locales/zh-Hans/app.json b/locales/zh-Hans/app.json index da450f15..58d405d0 100644 --- a/locales/zh-Hans/app.json +++ b/locales/zh-Hans/app.json @@ -92,8 +92,6 @@ "room_auth_view_eula_caption": "点击 \"加入通话\",即表示您同意我们的<2>最终用户许可协议 (EULA)", "screenshare_button_label": "屏幕共享", "settings": { - "developer_settings_label": "开发者设置", - "developer_settings_label_description": "在设置中显示开发者设置。", "developer_tab_title": "开发者", "feedback_tab_body": "如果遇到问题或想提供一些反馈意见,请在下面向我们发送简短描述。", "feedback_tab_description_label": "您的反馈", @@ -101,7 +99,6 @@ "feedback_tab_send_logs_label": "包含调试日志", "feedback_tab_thank_you": "谢谢,我们收到了反馈!", "feedback_tab_title": "反馈", - "more_tab_title": "更多", "opt_in_description": "<0><1>您可以取消选中复选框来撤回同意。如果正在通话中,此设置将在通话结束时生效。", "speaker_device_selection_label": "发言人" }, diff --git a/locales/zh-Hant/app.json b/locales/zh-Hant/app.json index f6bb2aa0..b73e4658 100644 --- a/locales/zh-Hant/app.json +++ b/locales/zh-Hant/app.json @@ -99,8 +99,6 @@ "room_auth_view_eula_caption": "點擊「立刻加入通話」即表示您同意我們的<2>終端使用者授權協議 (EULA)", "screenshare_button_label": "分享畫面", "settings": { - "developer_settings_label": "開發者設定", - "developer_settings_label_description": "在設定視窗中顯示開發者設定。", "developer_tab_title": "開發者", "feedback_tab_body": "若您遇到問題或只是想提供一些回饋,請在下方傳送簡短說明給我們。", "feedback_tab_description_label": "您的回饋", @@ -108,7 +106,6 @@ "feedback_tab_send_logs_label": "包含除錯紀錄", "feedback_tab_thank_you": "感謝,我們已經收到您的回饋了!", "feedback_tab_title": "回饋", - "more_tab_title": "更多", "opt_in_description": "<0><1>您可以透過取消核取此方塊來撤回同意。若您目前正在通話中,此設定將在通話結束時生效。", "speaker_device_selection_label": "發言者" }, From a723f10d2cdb71d25725bb6d2d2ac628b4f2ead0 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 13 Dec 2024 14:53:08 +0000 Subject: [PATCH 51/72] Developer setting to show LiveKit participants that do not have MatrixRTC sessions a.k.a. non-member tiles (#2771) * make tiles based on rtc member * display missing lk participant + fix tile multiplier * add show_non_member_participants config option * per member tiles * merge fixes * linter * linter and tests * tests * adapt tests (wip) * Remove unused keys * Fix optionality of nonMemberItemCount * video is optional * Mock RTC members * Lint * Merge fixes * Fix user id * Add explicit types for public fields * isRTCParticipantAvailable => isLiveKitParticipantAvailable * isLiveKitParticipantAvailable * Readonly * More keys removal * Make local field based on view model class not observable * Wording * Fix RTC members in tes * Tests again * Lint * Disable showing non-member tiles by default * Duplicate screen sharing tiles like we used to * Lint * Revert function reordering * Remove throttleTime from bad merge * Cleanup * Tidy config of show non-member settings * tidy up handling of local rtc member in tests * tidy up test init * Fix mocks * Cleanup * Apply local override where participant not yet known * Handle no visible media id * Assertions for one-on-one view * Remove isLiveKitParticipantAvailable and show via encryption status * Handle no local media (yet) * Remove unused effect for setting * Tidy settings * Avoid case of one-to-one layout with missing local or remote * Iterate * Remove option to show non-member tiles to simplify code review * Remove unused code * Remove more remnants of show-non-member-tiles * iterate * back * Fix unit test * Refactor * Expose TestScheduler as global * Fix incorrect type assertion * Simplify speaking observer * Fix * Whitespace * Make it clear that we are mocking MatrixRTC memberships * Test case for only showing tiles for MatrixRTC session members * Simplify diff * Simplify diff These changes are in https://github.com/element-hq/element-call/pull/2809 * . * Whitespaces * Use asObservable when exposing subject * Show "waiting for media..." when no participant * Additional test case * Don't show "waiting for media..." in case of local participant * Make the loading state more subtle - instead of a label we show a animated gradient * Use correct key for matrix rtc foci in code comment. (#2838) * Update src/tile/SpotlightTile.tsx Co-authored-by: Timo <16718859+toger5@users.noreply.github.com> * Update src/state/CallViewModel.ts Co-authored-by: Timo <16718859+toger5@users.noreply.github.com> * Make the purpose of BaseMediaViewModel.local explicit * Use named object instead of unnamed array for spotlightAndPip * Refactor spotlightAndPip into spotlight and pip * Use if statement instead of ternary for readability in spotlight and pip logic * Review feedback * Fix tests for CallEventAudioRenderer * Lint * Developer setting to show non-member tiles This is based on top of https://github.com/element-hq/element-call/pull/2701 * Lint * Remove unused code * Remove changes that should be in https://github.com/element-hq/element-call/pull/2858 * Update src/state/CallViewModel.test.ts Co-authored-by: Robin * Merge branch 'livekit' into toger5/show-non-member-tiles * Remove unused nonMemberItemCount * Restore default showNonMemberTiles value after test * Update comments --------- Co-authored-by: Timo Co-authored-by: Timo <16718859+toger5@users.noreply.github.com> Co-authored-by: Robin --- locales/en/app.json | 3 +- src/settings/DeveloperSettingsTab.tsx | 18 ++++++++ src/settings/settings.ts | 4 ++ src/state/CallViewModel.test.ts | 48 ++++++++++++++++++++ src/state/CallViewModel.ts | 64 +++++++++++++++++++++++++-- 5 files changed, 133 insertions(+), 4 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 84038c2b..33f8f921 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -74,7 +74,8 @@ "device_id": "Device ID: {{id}}", "duplicate_tiles_label": "Number of additional tile copies per participant", "hostname": "Hostname: {{hostname}}", - "matrix_id": "Matrix ID: {{id}}" + "matrix_id": "Matrix ID: {{id}}", + "show_non_member_tiles": "Show tiles for non-member media" }, "disconnected_banner": "Connectivity to the server has been lost.", "full_screen_view_description": "<0>Submitting debug logs will help us track down the problem.", diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index 209bc41e..057b0b0c 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -13,6 +13,7 @@ import { useSetting, duplicateTiles as duplicateTilesSetting, debugTileLayout as debugTileLayoutSetting, + showNonMemberTiles as showNonMemberTilesSetting, } from "./settings"; import type { MatrixClient } from "matrix-js-sdk/src/client"; @@ -26,6 +27,9 @@ export const DeveloperSettingsTab: FC = ({ client }) => { const [debugTileLayout, setDebugTileLayout] = useSetting( debugTileLayoutSetting, ); + const [showNonMemberTiles, setShowNonMemberTiles] = useSetting( + showNonMemberTilesSetting, + ); return ( <> @@ -85,6 +89,20 @@ export const DeveloperSettingsTab: FC = ({ client }) => { } /> + + ): void => { + setShowNonMemberTiles(event.target.checked); + }, + [setShowNonMemberTiles], + )} + /> + ); }; diff --git a/src/settings/settings.ts b/src/settings/settings.ts index e8dda379..a902f9ab 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -72,6 +72,10 @@ export const developerMode = new Setting("developer-settings-tab", false); export const duplicateTiles = new Setting("duplicate-tiles", 0); +export const showNonMemberTiles = new Setting( + "show-non-member-tiles", + false, +); export const debugTileLayout = new Setting("debug-tile-layout", false); export const audioInput = new Setting( diff --git a/src/state/CallViewModel.test.ts b/src/state/CallViewModel.test.ts index 0e45faa1..d5b84d49 100644 --- a/src/state/CallViewModel.test.ts +++ b/src/state/CallViewModel.test.ts @@ -46,6 +46,7 @@ import { type ECConnectionState, } from "../livekit/useECConnectionState"; import { E2eeType } from "../e2ee/e2eeType"; +import { showNonMemberTiles } from "../settings/settings"; vi.mock("@livekit/components-core"); @@ -686,6 +687,53 @@ test("participants must have a MatrixRTCSession to be visible", () => { }); }); +test("shows participants without MatrixRTCSession when enabled in settings", () => { + try { + // enable the setting: + showNonMemberTiles.setValue(true); + withTestScheduler(({ hot, expectObservable }) => { + const scenarioInputMarbles = " abc"; + const expectedLayoutMarbles = "abc"; + + withCallViewModel( + hot(scenarioInputMarbles, { + a: [], + b: [aliceParticipant], + c: [aliceParticipant, bobParticipant], + }), + of([]), // No one joins the MatrixRTC session + of(ConnectionState.Connected), + new Map(), + (vm) => { + vm.setGridMode("grid"); + expectObservable(summarizeLayout(vm.layout)).toBe( + expectedLayoutMarbles, + { + a: { + type: "grid", + spotlight: undefined, + grid: ["local:0"], + }, + b: { + type: "one-on-one", + local: "local:0", + remote: `${aliceId}:0`, + }, + c: { + type: "grid", + spotlight: undefined, + grid: ["local:0", `${aliceId}:0`, `${bobId}:0`], + }, + }, + ); + }, + ); + }); + } finally { + showNonMemberTiles.setValue(showNonMemberTiles.defaultValue); + } +}); + it("should show at least one tile per MatrixRTCSession", () => { withTestScheduler(({ hot, expectObservable }) => { // iterate through some combinations of MatrixRTC memberships diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 108a44c9..c701519b 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -69,7 +69,7 @@ import { } from "./MediaViewModel"; import { accumulate, finalizeValue } from "../utils/observable"; import { ObservableScope } from "./ObservableScope"; -import { duplicateTiles } from "../settings/settings"; +import { duplicateTiles, showNonMemberTiles } from "../settings/settings"; import { isFirefox } from "../Platform"; import { setPipEnabled } from "../controls"; import { @@ -449,6 +449,7 @@ export class CallViewModel extends ViewModel { this.matrixRTCSession, MatrixRTCSessionEvent.MembershipsChanged, ).pipe(startWith(null)), + showNonMemberTiles.value, ]).pipe( scan( ( @@ -458,6 +459,7 @@ export class CallViewModel extends ViewModel { { participant: localParticipant }, duplicateTiles, _membershipsChanged, + showNonMemberTiles, ], ) => { const newItems = new Map( @@ -495,9 +497,17 @@ export class CallViewModel extends ViewModel { } for (let i = 0; i < 1 + duplicateTiles; i++) { const indexedMediaId = `${livekitParticipantId}:${i}`; - const prevMedia = prevItems.get(indexedMediaId); + let prevMedia = prevItems.get(indexedMediaId); if (prevMedia && prevMedia instanceof UserMedia) { prevMedia.updateParticipant(participant); + if (prevMedia.vm.member === undefined) { + // We have a previous media created because of the `debugShowNonMember` flag. + // In this case we actually replace the media item. + // This "hack" never occurs if we do not use the `debugShowNonMember` debugging + // option and if we always find a room member for each rtc member (which also + // only fails if we have a fundamental problem) + prevMedia = undefined; + } } yield [ indexedMediaId, @@ -533,7 +543,55 @@ export class CallViewModel extends ViewModel { }.bind(this)(), ); - return newItems; + // Generate non member items (items without a corresponding MatrixRTC member) + // Those items should not be rendered, they are participants in LiveKit that do not have a corresponding + // MatrixRTC members. This cannot be any good: + // - A malicious user impersonates someone + // - Someone injects abusive content + // - The user cannot have encryption keys so it makes no sense to participate + // We can only trust users that have a MatrixRTC member event. + // + // This is still available as a debug option. This can be useful + // - If one wants to test scalability using the LiveKit CLI. + // - If an experimental project does not yet do the MatrixRTC bits. + // - If someone wants to debug if the LiveKit connection works but MatrixRTC room state failed to arrive. + const newNonMemberItems = showNonMemberTiles + ? new Map( + function* (this: CallViewModel): Iterable<[string, MediaItem]> { + for (const participant of remoteParticipants) { + for (let i = 0; i < 1 + duplicateTiles; i++) { + const maybeNonMemberParticipantId = + participant.identity + ":" + i; + if (!newItems.has(maybeNonMemberParticipantId)) { + const nonMemberId = maybeNonMemberParticipantId; + yield [ + nonMemberId, + prevItems.get(nonMemberId) ?? + new UserMedia( + nonMemberId, + undefined, + participant, + this.encryptionSystem, + this.livekitRoom, + ), + ]; + } + } + } + }.bind(this)(), + ) + : new Map(); + if (newNonMemberItems.size > 0) { + logger.debug("Added NonMember items: ", newNonMemberItems); + } + + const combinedNew = new Map([ + ...newNonMemberItems.entries(), + ...newItems.entries(), + ]); + + for (const [id, t] of prevItems) if (!combinedNew.has(id)) t.destroy(); + return combinedNew; }, new Map(), ), From de276b1fc33eca2a321ce6050c676bda5514552c Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 13 Dec 2024 15:22:44 -0500 Subject: [PATCH 52/72] Annotate the default device with a label --- locales/en/app.json | 1 + src/livekit/MediaDevicesContext.tsx | 7 ++- src/settings/DeviceSelection.module.css | 4 ++ src/settings/DeviceSelection.tsx | 75 +++++++++++++++++-------- 4 files changed, 62 insertions(+), 25 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 8e963ec1..a47e5beb 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -155,6 +155,7 @@ "camera": "Camera", "camera_numbered": "Camera {{n}}", "default": "Default", + "default_named": "Default <2>({{name}})", "microphone": "Microphone", "microphone_numbered": "Microphone {{n}}", "speaker": "Speaker", diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 513e6b2f..82b06bb0 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -31,7 +31,7 @@ import { export type DeviceLabel = | { type: "name"; name: string } | { type: "number"; number: number } - | { type: "default" }; + | { type: "default"; name: string | null }; export interface MediaDevice { /** @@ -104,7 +104,10 @@ function useMediaDevice( !available.has("") && !available.has("default") ) - available = new Map([["", { type: "default" }], ...available]); + available = new Map([ + ["", { type: "default", name: availableRaw[0]?.label || null }], + ...available, + ]); // Note: creating virtual default input devices would be another problem // entirely, because requesting a media stream from deviceId "" won't // automatically track the default device. diff --git a/src/settings/DeviceSelection.module.css b/src/settings/DeviceSelection.module.css index daa4510e..6686702f 100644 --- a/src/settings/DeviceSelection.module.css +++ b/src/settings/DeviceSelection.module.css @@ -16,3 +16,7 @@ flex-direction: column; gap: var(--cpd-space-4x); } + +.secondary { + color: var(--cpd-color-text-secondary); +} diff --git a/src/settings/DeviceSelection.tsx b/src/settings/DeviceSelection.tsx index 9faa1e82..aebe0aac 100644 --- a/src/settings/DeviceSelection.tsx +++ b/src/settings/DeviceSelection.tsx @@ -5,7 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { type ChangeEvent, type FC, useCallback, useId } from "react"; +import { + type ChangeEvent, + type FC, + type ReactElement, + type ReactNode, + useCallback, + useId, +} from "react"; import { Heading, InlineField, @@ -13,7 +20,7 @@ import { RadioControl, Separator, } from "@vector-im/compound-web"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { type MediaDevice } from "../livekit/MediaDevicesContext"; import styles from "./DeviceSelection.module.css"; @@ -53,27 +60,49 @@ export const DeviceSelection: FC = ({
- {[...devices.available].map(([id, label]) => ( - - } - > - - - ))} + {[...devices.available].map(([id, label]) => { + let labelText: ReactNode; + switch (label.type) { + case "name": + labelText = label.name; + break; + case "number": + labelText = numberedLabel(label.number); + break; + case "default": + labelText = + label.name === null ? ( + t("settings.devices.default") + ) : ( + + Default{" "} + + ({{ name: label.name } as unknown as ReactElement}) + + + ); + break; + } + + return ( + + } + > + + + ); + })}
); From f9e3fe31762504bc610d690e53aef3a4585ee0af Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 13 Dec 2024 15:37:29 -0500 Subject: [PATCH 53/72] Use observables for more of the media devices logic --- src/livekit/MediaDevicesContext.tsx | 83 +++++++++++++++++------------ 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 82b06bb0..9edbb884 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -16,7 +16,7 @@ import { useState, } from "react"; import { createMediaDeviceObserver } from "@livekit/components-core"; -import { startWith } from "rxjs"; +import { map, startWith } from "rxjs"; import { useObservableEagerState } from "observable-hooks"; import { logger } from "matrix-js-sdk/src/logger"; @@ -83,36 +83,43 @@ function useMediaDevice( ).pipe(startWith([])), [kind, requestPermissions], ); - const availableRaw = useObservableEagerState(deviceObserver); - const available = useMemo(() => { - // Sometimes browsers (particularly Firefox) can return multiple device - // entries for the exact same device ID; using a map deduplicates them - let available = new Map( - availableRaw.map((d, i) => [ - d.deviceId, - d.label - ? { type: "name", name: d.label } - : { type: "number", number: i + 1 }, - ]), - ); - // Create a virtual default audio output for browsers that don't have one. - // Its device ID must be the empty string because that's what setSinkId - // recognizes. - if ( - kind === "audiooutput" && - available.size && - !available.has("") && - !available.has("default") - ) - available = new Map([ - ["", { type: "default", name: availableRaw[0]?.label || null }], - ...available, - ]); - // Note: creating virtual default input devices would be another problem - // entirely, because requesting a media stream from deviceId "" won't - // automatically track the default device. - return available; - }, [kind, availableRaw]); + const available = useObservableEagerState( + useMemo( + () => + deviceObserver.pipe( + map((availableRaw) => { + // Sometimes browsers (particularly Firefox) can return multiple device + // entries for the exact same device ID; using a map deduplicates them + let available = new Map( + availableRaw.map((d, i) => [ + d.deviceId, + d.label + ? { type: "name", name: d.label } + : { type: "number", number: i + 1 }, + ]), + ); + // Create a virtual default audio output for browsers that don't have one. + // Its device ID must be the empty string because that's what setSinkId + // recognizes. + if ( + kind === "audiooutput" && + available.size && + !available.has("") && + !available.has("default") + ) + available = new Map([ + ["", { type: "default", name: availableRaw[0]?.label || null }], + ...available, + ]); + // Note: creating virtual default input devices would be another problem + // entirely, because requesting a media stream from deviceId "" won't + // automatically track the default device. + return available; + }), + ), + [kind, deviceObserver], + ), + ); const [preferredId, select] = useSetting(setting); const selectedId = useMemo(() => { @@ -130,9 +137,17 @@ function useMediaDevice( } return undefined; }, [available, preferredId]); - const selectedGroupId = useMemo( - () => availableRaw.find((d) => d.deviceId === selectedId)?.groupId, - [availableRaw, selectedId], + const selectedGroupId = useObservableEagerState( + useMemo( + () => + deviceObserver.pipe( + map( + (availableRaw) => + availableRaw.find((d) => d.deviceId === selectedId)?.groupId, + ), + ), + [deviceObserver, selectedId], + ), ); return useMemo( From 25d0338f35b2c354053085bac48dbaeedec42a3d Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 13 Dec 2024 16:40:20 -0500 Subject: [PATCH 54/72] Skip some redundant updates to the grid and spotlight While debugging our layout shift issue I learned that a single change to the sort order of the participants can cause 3 or 4 redundant emissions of the same items in the same order. Since each of these would cause React to re-render the grid, skipping these spurious emissions seems like an easy performance win. --- src/state/CallViewModel.ts | 4 ++++ src/utils/array.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/utils/array.ts diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index c701519b..8e080fdc 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -83,6 +83,7 @@ import { oneOnOneLayout } from "./OneOnOneLayout"; import { pipLayout } from "./PipLayout"; import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; import { observeSpeaker } from "./observeSpeaker"; +import { shallowEquals } from "../utils/array"; // How long we wait after a focus switch before showing the real participant // list again @@ -705,6 +706,8 @@ export class CallViewModel extends ViewModel { bins.sort(([, bin1], [, bin2]) => bin1 - bin2).map(([m]) => m.vm), ); }), + distinctUntilChanged(shallowEquals), + this.scope.state(), ); private readonly spotlight: Observable = @@ -718,6 +721,7 @@ export class CallViewModel extends ViewModel { map((speaker) => (speaker ? [speaker] : [])), ); }), + distinctUntilChanged(shallowEquals), this.scope.state(), ); diff --git a/src/utils/array.ts b/src/utils/array.ts new file mode 100644 index 00000000..70ecbd89 --- /dev/null +++ b/src/utils/array.ts @@ -0,0 +1,16 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +/** + * Determine whether two arrays are equal by shallow comparison. + */ +export function shallowEquals
(first: A[], second: A[]): boolean { + if (first.length !== second.length) return false; + for (let i = 0; i < first.length; i++) + if (first[i] !== second[i]) return false; + return true; +} From 92afd5d63ae2914f651a3eef93554b3cbf4abb40 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 16 Dec 2024 11:22:23 +0000 Subject: [PATCH 55/72] Wait for .well-known/matrix/client to load before determining MatrixRTC foci (#2901) --- src/rtcSessionHelper.test.ts | 2 +- src/rtcSessionHelpers.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rtcSessionHelper.test.ts b/src/rtcSessionHelper.test.ts index 7df9f1b3..729545f7 100644 --- a/src/rtcSessionHelper.test.ts +++ b/src/rtcSessionHelper.test.ts @@ -40,7 +40,7 @@ test("It joins the correct Session", async () => { room: { roomId: "roomId", client: { - getClientWellKnown: vi.fn().mockReturnValue(clientWellKnown), + waitForClientWellKnown: vi.fn().mockResolvedValue(clientWellKnown), }, }, memberships: [], diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index f1c7eb8c..680df571 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -44,8 +44,9 @@ async function makePreferredLivekitFoci( } // Prioritize the client well known over the configured sfu. - const wellKnownFoci = - rtcSession.room.client.getClientWellKnown()?.[FOCI_WK_KEY]; + const wellKnownFoci = ( + await rtcSession.room.client.waitForClientWellKnown() + )?.[FOCI_WK_KEY]; if (Array.isArray(wellKnownFoci)) { preferredFoci.push( ...wellKnownFoci From b822b9f80df3e306d576289abfea127802a3f09e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:44:57 +0100 Subject: [PATCH 56/72] Update all non-major dependencies (#2900) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 527 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 300 insertions(+), 227 deletions(-) diff --git a/yarn.lock b/yarn.lock index d607f394..5fdd147f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2340,6 +2340,11 @@ resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA== +"@radix-ui/primitive@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.1.tgz#fc169732d755c7fbad33ba8d0cd7fd10c90dc8e3" + integrity sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA== + "@radix-ui/react-arrow@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz#744f388182d360b86285217e43b6c63633f39e7a" @@ -2357,11 +2362,26 @@ "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-slot" "1.1.0" +"@radix-ui/react-collection@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.1.tgz#be2c7e01d3508e6d4b6d838f492e7d182f17d3b0" + integrity sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA== + dependencies: + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-slot" "1.1.1" + "@radix-ui/react-compose-refs@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74" integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw== +"@radix-ui/react-compose-refs@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz#6f766faa975f8738269ebb8a23bad4f5a8d2faec" + integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw== + "@radix-ui/react-context-menu@^2.2.1": version "2.2.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.2.tgz#efcddc559fc3011721b65148f062d04027f76c7a" @@ -2385,21 +2405,21 @@ integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q== "@radix-ui/react-dialog@^1.0.4", "@radix-ui/react-dialog@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz#d9345575211d6f2d13e209e84aec9a8584b54d6c" - integrity sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA== + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.3.tgz#87cf49f619a6a0f6219980678be0f7c31978dee1" + integrity sha512-ujGvqQNkZ0J7caQyl8XuZRj2/TIrYcOGwqz5TeD1OMcCdfBuEMP0D12ve+8J5F9XuNUth3FAKFWo/wt0E/GJrQ== dependencies: - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-context" "1.1.1" - "@radix-ui/react-dismissable-layer" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.2" "@radix-ui/react-focus-guards" "1.1.1" - "@radix-ui/react-focus-scope" "1.1.0" + "@radix-ui/react-focus-scope" "1.1.1" "@radix-ui/react-id" "1.1.0" - "@radix-ui/react-portal" "1.1.2" - "@radix-ui/react-presence" "1.1.1" - "@radix-ui/react-primitive" "2.0.0" - "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-portal" "1.1.3" + "@radix-ui/react-presence" "1.1.2" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-slot" "1.1.1" "@radix-ui/react-use-controllable-state" "1.1.0" aria-hidden "^1.1.1" react-remove-scroll "2.6.0" @@ -2420,6 +2440,17 @@ "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-escape-keydown" "1.1.0" +"@radix-ui/react-dismissable-layer@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.2.tgz#771594b202f32bc8ffeb278c565f10c513814aee" + integrity sha512-kEHnlhv7wUggvhuJPkyw4qspXLJOdYoAP4dO2c8ngGuXTq1w/HZp1YeVB+NQ2KbH1iEG+pvOCGYSqh9HZOz6hg== + dependencies: + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-escape-keydown" "1.1.0" + "@radix-ui/react-dropdown-menu@^2.1.1": version "2.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz#acc49577130e3c875ef0133bd1e271ea3392d924" @@ -2447,6 +2478,15 @@ "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-use-callback-ref" "1.1.0" +"@radix-ui/react-focus-scope@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz#5c602115d1db1c4fcfa0fae4c3b09bb8919853cb" + integrity sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA== + dependencies: + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-form@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-form/-/react-form-0.1.0.tgz#7111a6aa54a2bde0d11fb72643f9ffc871ac58ad" @@ -2521,6 +2561,14 @@ "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-portal@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.3.tgz#b0ea5141103a1671b715481b13440763d2ac4440" + integrity sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw== + dependencies: + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-presence@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1" @@ -2529,6 +2577,14 @@ "@radix-ui/react-compose-refs" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-presence@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.2.tgz#bb764ed8a9118b7ec4512da5ece306ded8703cdc" + integrity sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg== + dependencies: + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-primitive@2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" @@ -2536,6 +2592,13 @@ dependencies: "@radix-ui/react-slot" "1.1.0" +"@radix-ui/react-primitive@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz#6d9efc550f7520135366f333d1e820cf225fad9e" + integrity sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg== + dependencies: + "@radix-ui/react-slot" "1.1.1" + "@radix-ui/react-progress@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.1.0.tgz#28c267885ec154fc557ec7a66cb462787312f7e2" @@ -2567,17 +2630,17 @@ "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-slider@^1.1.2": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-1.2.1.tgz#acb0804309890f3cd7a224b2b0c4c4704f32921b" - integrity sha512-bEzQoDW0XP+h/oGbutF5VMWJPAl/UU8IJjr7h02SOHDIIIxq+cep8nItVNoBV+OMmahCdqdF38FTpmXoqQUGvw== + version "1.2.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-1.2.2.tgz#4ca883e3f0dea7b97d43c6cbc6c4305c64e75a86" + integrity sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA== dependencies: "@radix-ui/number" "1.1.0" - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-collection" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-collection" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-context" "1.1.1" "@radix-ui/react-direction" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-use-previous" "1.1.0" @@ -2590,6 +2653,13 @@ dependencies: "@radix-ui/react-compose-refs" "1.1.0" +"@radix-ui/react-slot@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.1.tgz#ab9a0ffae4027db7dc2af503c223c978706affc3" + integrity sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g== + dependencies: + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-use-callback-ref@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1" @@ -2634,11 +2704,11 @@ "@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-visually-hidden@^1.0.3": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2" - integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz#f7b48c1af50dfdc366e92726aee6d591996c5752" + integrity sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg== dependencies: - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/rect@1.1.0": version "1.1.0" @@ -2704,231 +2774,236 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz#3e7eda4c0c1de6d2415343002d742ff95e38dca7" integrity sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA== -"@rollup/rollup-android-arm-eabi@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.0.tgz#462e7ecdd60968bc9eb95a20d185e74f8243ec1b" - integrity sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ== +"@rollup/rollup-android-arm-eabi@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz#7f4c4d8cd5ccab6e95d6750dbe00321c1f30791e" + integrity sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ== "@rollup/rollup-android-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz#04f679231acf7284f1f8a1f7250d0e0944865ba8" integrity sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg== -"@rollup/rollup-android-arm64@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.0.tgz#78a2b8a8a55f71a295eb860a654ae90a2b168f40" - integrity sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA== +"@rollup/rollup-android-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz#17ea71695fb1518c2c324badbe431a0bd1879f2d" + integrity sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA== "@rollup/rollup-darwin-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz#ecea723041621747d0772af93b54752edf26467a" integrity sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg== -"@rollup/rollup-darwin-arm64@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.0.tgz#5b783af714f434f1e66e3cdfa3817e0b99216d84" - integrity sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q== +"@rollup/rollup-darwin-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz#dac0f0d0cfa73e7d5225ae6d303c13c8979e7999" + integrity sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ== "@rollup/rollup-darwin-x64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz#28e6e0687092f31e20982fc104779d48c643fc21" integrity sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA== -"@rollup/rollup-darwin-x64@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.0.tgz#f72484e842521a5261978034e18e20f778a2850d" - integrity sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w== +"@rollup/rollup-darwin-x64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz#8f63baa1d31784904a380d2e293fa1ddf53dd4a2" + integrity sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ== "@rollup/rollup-freebsd-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz#99e9173b8aef3d1ef086983da70413988206e530" integrity sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g== -"@rollup/rollup-freebsd-arm64@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.0.tgz#3c919dff72b2fe344811a609c674a8347b033f62" - integrity sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ== +"@rollup/rollup-freebsd-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz#30ed247e0df6e8858cdc6ae4090e12dbeb8ce946" + integrity sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA== "@rollup/rollup-freebsd-x64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz#f3a1ef941f8d3c6b2b036484c69a7b2d3d9ebbd7" integrity sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw== -"@rollup/rollup-freebsd-x64@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.0.tgz#b62a3a8365b363b3fdfa6da11a9188b6ab4dca7c" - integrity sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA== +"@rollup/rollup-freebsd-x64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz#57846f382fddbb508412ae07855b8a04c8f56282" + integrity sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ== "@rollup/rollup-linux-arm-gnueabihf@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz#9ba6adcc33f26f2a0c6ee658f0bbda4de8da2f75" integrity sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA== -"@rollup/rollup-linux-arm-gnueabihf@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.0.tgz#0d02cc55bd229bd8ca5c54f65f916ba5e0591c94" - integrity sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w== +"@rollup/rollup-linux-arm-gnueabihf@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz#378ca666c9dae5e6f94d1d351e7497c176e9b6df" + integrity sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA== "@rollup/rollup-linux-arm-musleabihf@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz#62f2426fa9016ec884f4fa779d7b62d5ba02a41a" integrity sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ== -"@rollup/rollup-linux-arm-musleabihf@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.0.tgz#c51d379263201e88a60e92bd8e90878f0c044425" - integrity sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg== +"@rollup/rollup-linux-arm-musleabihf@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz#a692eff3bab330d5c33a5d5813a090c15374cddb" + integrity sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg== "@rollup/rollup-linux-arm64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz#f98ec111a231d35e0c6d3404e3d80f67f9d5b9f8" integrity sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A== -"@rollup/rollup-linux-arm64-gnu@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.0.tgz#93ce2addc337b5cfa52b84f8e730d2e36eb4339b" - integrity sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg== +"@rollup/rollup-linux-arm64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz#6b1719b76088da5ac1ae1feccf48c5926b9e3db9" + integrity sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA== "@rollup/rollup-linux-arm64-musl@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz#4b36ffb8359f959f2c29afd187603c53368b6723" integrity sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw== -"@rollup/rollup-linux-arm64-musl@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.0.tgz#730af6ddc091a5ba5baac28a3510691725dc808b" - integrity sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw== +"@rollup/rollup-linux-arm64-musl@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz#865baf5b6f5ff67acb32e5a359508828e8dc5788" + integrity sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A== + +"@rollup/rollup-linux-loongarch64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz#23c6609ba0f7fa7a7f2038b6b6a08555a5055a87" + integrity sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA== "@rollup/rollup-linux-powerpc64le-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz#52f4b39e6783505d168a745b79d86474fde71680" integrity sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA== -"@rollup/rollup-linux-powerpc64le-gnu@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.0.tgz#b5565aac20b4de60ca1e557f525e76478b5436af" - integrity sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ== +"@rollup/rollup-linux-powerpc64le-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz#652ef0d9334a9f25b9daf85731242801cb0fc41c" + integrity sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A== "@rollup/rollup-linux-riscv64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz#49195be7e6a7d68d482b12461e2ea914e31ff977" integrity sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA== -"@rollup/rollup-linux-riscv64-gnu@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.0.tgz#d488290bf9338bad4ae9409c4aa8a1728835a20b" - integrity sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g== +"@rollup/rollup-linux-riscv64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz#1eb6651839ee6ebca64d6cc64febbd299e95e6bd" + integrity sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA== "@rollup/rollup-linux-s390x-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz#4b8d50a205eac7b46cdcb9c50d4a6ae5994c02e0" integrity sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ== -"@rollup/rollup-linux-s390x-gnu@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.0.tgz#eb2e3f3a06acf448115045c11a5a96868c95a556" - integrity sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw== +"@rollup/rollup-linux-s390x-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz#015c52293afb3ff2a293cf0936b1d43975c1e9cd" + integrity sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg== "@rollup/rollup-linux-x64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz#dfcceebc5ccac7fc2db19471996026258c81b55f" integrity sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig== -"@rollup/rollup-linux-x64-gnu@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz#065952ef2aea7e837dc7e02aa500feeaff4fc507" - integrity sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw== +"@rollup/rollup-linux-x64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz#b83001b5abed2bcb5e2dbeec6a7e69b194235c1e" + integrity sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw== "@rollup/rollup-linux-x64-musl@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz#192f78bad8429711d63a31dc0a7d3312e2df850e" integrity sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ== -"@rollup/rollup-linux-x64-musl@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.0.tgz#3435d484d05f5c4d1ffd54541b4facce2887103a" - integrity sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw== +"@rollup/rollup-linux-x64-musl@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz#6cc7c84cd4563737f8593e66f33b57d8e228805b" + integrity sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g== "@rollup/rollup-win32-arm64-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz#f4ec076579634f780b4e5896ae7f59f3e38e0c60" integrity sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww== -"@rollup/rollup-win32-arm64-msvc@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.0.tgz#69682a2a10d9fedc334f87583cfca83c39c08077" - integrity sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg== +"@rollup/rollup-win32-arm64-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz#631ffeee094d71279fcd1fe8072bdcf25311bc11" + integrity sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A== "@rollup/rollup-win32-ia32-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz#5458eab1929827e4f805cefb90bd09ecf7eeed2b" integrity sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg== -"@rollup/rollup-win32-ia32-msvc@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.0.tgz#b64470f9ac79abb386829c56750b9a4711be3332" - integrity sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A== +"@rollup/rollup-win32-ia32-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz#06d1d60d5b9f718e8a6c4a43f82e3f9e3254587f" + integrity sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA== "@rollup/rollup-win32-x64-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz#93415e7e707e4b156d77c5950b983b58f4bc33f3" integrity sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg== -"@rollup/rollup-win32-x64-msvc@4.28.0": - version "4.28.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.0.tgz#cb313feef9ac6e3737067fdf34f42804ac65a6f2" - integrity sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ== +"@rollup/rollup-win32-x64-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz#4dff5c4259ebe6c5b4a8f2c5bc3829b7a8447ff0" + integrity sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA== "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sentry-internal/browser-utils@8.43.0": - version "8.43.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.43.0.tgz#b064908a537d1cc17d8ddaf0f4c5d712557cbf40" - integrity sha512-5WhJZ3SA5sZVDBwOsChDd5JCzYcwBX7sEqBqEcm3pFru6TUihEnFIJmDIbreIyrQMwUhs3dTxnfnidgjr5z1Ag== +"@sentry-internal/browser-utils@8.45.0": + version "8.45.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.45.0.tgz#8e9217b8e8a4242c9a8244dce648289eaa1e38a0" + integrity sha512-MX/E/C+W5I9jkGD1PsbZ2hpCc7YuizNKmEbuGPxQPfUSIPrdE2wpo6ZfIhEbxq9m/trl1oRCN4PXi3BB7dlYYg== dependencies: - "@sentry/core" "8.43.0" + "@sentry/core" "8.45.0" -"@sentry-internal/feedback@8.43.0": - version "8.43.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.43.0.tgz#9477b999c9bca62335eb944a6f7246a96beb0111" - integrity sha512-rcGR2kzFu4vLXBQbI9eGJwjyToyjl36O2q/UKbiZBNJ5IFtDvKRLke6jIHq/YqiHPfFGpVtq5M/lYduDfA/eaQ== +"@sentry-internal/feedback@8.45.0": + version "8.45.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.45.0.tgz#cfd7f54d5089682a2768c1229a5efcda4d9561fe" + integrity sha512-WerpfkKrKPAlnQuqjEgKXZtrx68cla7GyOkNOeL40JQbY4/By4Qjx1atUOmgk/FdjrCLPw+jQQY9pXRpMRqqRw== dependencies: - "@sentry/core" "8.43.0" + "@sentry/core" "8.45.0" -"@sentry-internal/replay-canvas@8.43.0": - version "8.43.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.43.0.tgz#f5672a08c9eb588afa0bf36f07b9f5c29b5c9920" - integrity sha512-rL8G7E1GtozH8VNalRrBQNjYDJ5ChWS/vpQI5hUG11PZfvQFXEVatLvT3uO2l0xIlHm4idTsHOSLTe/usxnogQ== +"@sentry-internal/replay-canvas@8.45.0": + version "8.45.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.45.0.tgz#46f39402ff0cfee4ae05191af20b4e4fac6f474c" + integrity sha512-LZ8kBuzO5gutDiWnCyYEzBMDLq9PIllcsWsXRpKoau0Zqs3DbyRolI11dNnxmUSh7UW21FksxBpqn5yPmUMbag== dependencies: - "@sentry-internal/replay" "8.43.0" - "@sentry/core" "8.43.0" + "@sentry-internal/replay" "8.45.0" + "@sentry/core" "8.45.0" -"@sentry-internal/replay@8.43.0": - version "8.43.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.43.0.tgz#4e2e3844f52b47b16bf816d21857921bbfe85d62" - integrity sha512-geV5/zejLfGGwWHjylzrb1w8NI3U37GMG9/53nmv13FmTXUDF5XF2lh41KXFVYwvp7Ha4bd1FRQ9IU9YtBWskw== +"@sentry-internal/replay@8.45.0": + version "8.45.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.45.0.tgz#e94d250de235491888694f7cf0f637114adb4b9a" + integrity sha512-SOFwFpzx0B6lxhLl2hBnxvybo7gdB5TMY8dOHMwXgk5A2+BXvSpvWXnr33yqUlBmC8R3LeFTB3C0plzM5lhkJg== dependencies: - "@sentry-internal/browser-utils" "8.43.0" - "@sentry/core" "8.43.0" + "@sentry-internal/browser-utils" "8.45.0" + "@sentry/core" "8.45.0" "@sentry/babel-plugin-component-annotate@2.22.7": version "2.22.7" resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.7.tgz#604c7e33d48528a13477e7af597c4d5fca51b8bd" integrity sha512-aa7XKgZMVl6l04NY+3X7BP7yvQ/s8scn8KzQfTLrGRarziTlMGrsCOBQtCNWXOPEbtxAIHpZ9dsrAn5EJSivOQ== -"@sentry/browser@8.43.0": - version "8.43.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.43.0.tgz#4eec67bc6fb278727304045b612ac392674cade6" - integrity sha512-LGvLLnfmR8+AEgFmd7Q7KHiOTiV0P1Lvio2ENDELhEqJOIiICauttibVmig+AW02qg4kMeywvleMsUYaZv2RVA== +"@sentry/browser@8.45.0": + version "8.45.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.45.0.tgz#2e8f7b8b1a7860863aae4d716b9748a21789f0e0" + integrity sha512-Y+BcfpXY1eEkOYOzgLGkx1YH940uMAymYOxfSZSvC+Vx6xHuaGT05mIFef/aeZbyu2AUs6JjdvD1BRBZlHg78w== dependencies: - "@sentry-internal/browser-utils" "8.43.0" - "@sentry-internal/feedback" "8.43.0" - "@sentry-internal/replay" "8.43.0" - "@sentry-internal/replay-canvas" "8.43.0" - "@sentry/core" "8.43.0" + "@sentry-internal/browser-utils" "8.45.0" + "@sentry-internal/feedback" "8.45.0" + "@sentry-internal/replay" "8.45.0" + "@sentry-internal/replay-canvas" "8.45.0" + "@sentry/core" "8.45.0" "@sentry/bundler-plugin-core@2.22.7": version "2.22.7" @@ -2998,18 +3073,18 @@ "@sentry/cli-win32-i686" "2.39.1" "@sentry/cli-win32-x64" "2.39.1" -"@sentry/core@8.43.0": - version "8.43.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.43.0.tgz#e96a489e87a9999199f5ac27d8860da37c1fa8b4" - integrity sha512-ktyovtjkTMNud+kC/XfqHVCoQKreIKgx/hgeRvzPwuPyd1t1KzYmRL3DBkbcWVnyOPpVTHn+RsEI1eRcVYHtvw== +"@sentry/core@8.45.0": + version "8.45.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.45.0.tgz#a03a1b666989898ce7fb33f9ec279ea08450b317" + integrity sha512-4YTuBipWSh4JrtSYS5GxUQBAcAgOIkEoFfFbwVcr3ivijOacJLRXTBn3rpcy1CKjBq0PHDGR+2RGRYC+bNAMxg== "@sentry/react@^8.0.0": - version "8.43.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.43.0.tgz#ad49bd16b0b1897613ef5cbd2f0a49b2b41f98a9" - integrity sha512-PsTzLrYio/FOJU537Y5Gj9jJi7OMHEjdttsC9INUxy5062LOd8ObtHsjE0mopLaSYEwUfSROQOBZCwmISh8ByQ== + version "8.45.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.45.0.tgz#9a1bfbbbb3575fffb92796acc28ad5bb93a6855a" + integrity sha512-xuJBDATJKAHOxpR5IBfGFWJxXb05GMPGGpk8UoWai1Mh50laAQ0/WW+5sDAKrCjXoA+JZ6fb3DP8EE2X93n1nw== dependencies: - "@sentry/browser" "8.43.0" - "@sentry/core" "8.43.0" + "@sentry/browser" "8.45.0" + "@sentry/core" "8.45.0" hoist-non-react-statics "^3.3.2" "@sentry/vite-plugin@^2.0.0": @@ -3273,9 +3348,9 @@ undici-types "~6.19.8" "@types/node@^22.0.0": - version "22.10.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.1.tgz#41ffeee127b8975a05f8c4f83fb89bcb2987d766" - integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== + version "22.10.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.2.tgz#a485426e6d1fdafc7b0d4c7b24e2c78182ddabb9" + integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== dependencies: undici-types "~6.20.0" @@ -3302,9 +3377,9 @@ "@types/node" "*" "@types/react-dom@^18.3.0": - version "18.3.3" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.3.tgz#3654138d0da1b0c7916f6ed0dc1cc2b576d47650" - integrity sha512-uTYkxTLkYp41nq/ULXyXMtkNT1vu5fXJoqad6uTNCOGat5t9cLgF4vMNLBXsTOXpdOI44XzKPY1M5RRm0bQHuw== + version "18.3.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.5.tgz#45f9f87398c5dcea085b715c58ddcf1faf65f716" + integrity sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q== "@types/react-router-dom@^5.3.3": version "5.3.3" @@ -4040,7 +4115,17 @@ broccoli-plugin@^4.0.7: rimraf "^3.0.2" symlink-or-copy "^1.3.1" -browserslist@^4.23.1, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.2: +browserslist@^4.23.1, browserslist@^4.23.3: + version "4.24.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2" + integrity sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + +browserslist@^4.24.0, browserslist@^4.24.2: version "4.24.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== @@ -4106,15 +4191,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001646: - version "1.0.30001680" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz#5380ede637a33b9f9f1fc6045ea99bd142f3da5e" - integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== - -caniuse-lite@^1.0.30001669: - version "1.0.30001687" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz#d0ac634d043648498eedf7a3932836beba90ebae" - integrity sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ== +caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669, caniuse-lite@^1.0.30001688: + version "1.0.30001688" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz#f9d3ede749f083ce0db4c13db9d828adaf2e8d0a" + integrity sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA== caseless@~0.12.0: version "0.12.0" @@ -4405,10 +4485,10 @@ css-blank-pseudo@^7.0.1: dependencies: postcss-selector-parser "^7.0.0" -css-has-pseudo@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-7.0.1.tgz#adbb51821e51f7a7c1d2df4d12827870cc311137" - integrity sha512-EOcoyJt+OsuKfCADgLT7gADZI5jMzIe/AeI6MeAYKiFBDmNmM7kk46DtSfMj5AohUJisqVzopBpnQTlvbyaBWg== +css-has-pseudo@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz#fb42e8de7371f2896961e1f6308f13c2c7019b72" + integrity sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ== dependencies: "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" @@ -4440,10 +4520,10 @@ css.escape@^1.5.1: resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== -cssdb@^8.2.1: - version "8.2.1" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-8.2.1.tgz#62a5d9a41e2c86f1d7c35981098fc5ce47c5766c" - integrity sha512-KwEPys7lNsC8OjASI8RrmwOYYDcm0JOW9zQhcV83ejYcQkirTEyeAGui8aO2F5PiS6SLpxuTzl6qlMElIdsgIg== +cssdb@^8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-8.2.3.tgz#7e6980bb5a785a9b4eb2a21bd38d50624b56cb46" + integrity sha512-9BDG5XmJrJQQnJ51VFxXCAtpZ5ebDlAREmO8sxMOVU0aSxN/gocbctjIG5LMh3WBUq+xTlb/jw2LoljBEqraTA== cssesc@^3.0.0: version "3.0.0" @@ -4703,10 +4783,10 @@ easy-table@1.2.0: optionalDependencies: wcwidth "^1.0.1" -electron-to-chromium@^1.5.41: - version "1.5.72" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz#a732805986d3a5b5fedd438ddf4616c7d78ac2df" - integrity sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw== +electron-to-chromium@^1.5.41, electron-to-chromium@^1.5.73: + version "1.5.73" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz#f32956ce40947fa3c8606726a96cd8fb5bb5f720" + integrity sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg== emoji-regex@^8.0.0: version "8.0.0" @@ -5867,13 +5947,6 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" @@ -6294,9 +6367,9 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== knip@^5.27.2: - version "5.39.2" - resolved "https://registry.yarnpkg.com/knip/-/knip-5.39.2.tgz#1faacd8d8ef36b509b2f6e396cce85b645abb04e" - integrity sha512-BuvuWRllLWV/r2G4m9ggNH+DZ6gouP/dhtJPXVlMbWNF++w9/EfrF6k2g7YBKCwjzCC+PXmYtpH8S2t8RjnY4Q== + version "5.40.0" + resolved "https://registry.yarnpkg.com/knip/-/knip-5.40.0.tgz#6da9113d9d0c696fc3e5dc3f3a281db57b4b828a" + integrity sha512-EzBfQDz4YBzYnMLueWnaaVr15mneqZs1c3RanttciuVuRcodlNjzAmR2nch/khlRdVABAxAdMGFxfSvhvcH1NA== dependencies: "@nodelib/fs.walk" "1.2.8" "@snyk/github-codeowners" "1.1.0" @@ -6414,7 +6487,7 @@ long@^5.0.0: resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6603,9 +6676,9 @@ ms@^2.1.1, ms@^2.1.3: integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== natural-compare@^1.4.0: version "1.4.0" @@ -6632,7 +6705,7 @@ node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" -node-releases@^2.0.18: +node-releases@^2.0.18, node-releases@^2.0.19: version "2.0.19" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== @@ -7133,9 +7206,9 @@ postcss-place@^10.0.0: postcss-value-parser "^4.2.0" postcss-preset-env@^10.0.0: - version "10.1.1" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.1.1.tgz#6ee631272353fb1c4a9711943e9b80a178ffce44" - integrity sha512-wqqsnBFD6VIwcHHRbhjTOcOi4qRVlB26RwSr0ordPj7OubRRxdWebv/aLjKLRR8zkZrbxZyuus03nOIgC5elMQ== + version "10.1.2" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.1.2.tgz#ea9c25d92045ef06edd78f9945d2586107aab3e3" + integrity sha512-OqUBZ9ByVfngWhMNuBEMy52Izj07oIFA6K/EOGBlaSv+P12MiE1+S2cqXtS1VuW82demQ/Tzc7typYk3uHunkA== dependencies: "@csstools/postcss-cascade-layers" "^5.0.1" "@csstools/postcss-color-function" "^4.0.6" @@ -7172,9 +7245,9 @@ postcss-preset-env@^10.0.0: autoprefixer "^10.4.19" browserslist "^4.23.1" css-blank-pseudo "^7.0.1" - css-has-pseudo "^7.0.1" + css-has-pseudo "^7.0.2" css-prefers-color-scheme "^10.0.0" - cssdb "^8.2.1" + cssdb "^8.2.3" postcss-attribute-case-insensitive "^7.0.1" postcss-clamp "^4.1.0" postcss-color-functional-notation "^7.0.6" @@ -7386,9 +7459,9 @@ react-error-boundary@^3.1.0: "@babel/runtime" "^7.12.5" react-i18next@^15.0.0: - version "15.1.4" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.1.4.tgz#65c03c31a5e42202000652e163f22f23a9306a60" - integrity sha512-2tai71gmehbvl9ZIqPMqlCCkm/cbeV1G4STpmM3C8Uzo6T2l8jDvZxEVSsQKt8blP9X34iRFP/k1ROqG2296MQ== + version "15.2.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.2.0.tgz#6b51650e1e93eb4d235a4d533fcf61b3bbf4ea10" + integrity sha512-iJNc8111EaDtVTVMKigvBtPHyrJV+KblWG73cUxqp+WmJCcwkzhWNFXmkAD5pwP2Z4woeDj/oXDdbjDsb3Gutg== dependencies: "@babel/runtime" "^7.25.0" html-parse-stringify "^3.0.1" @@ -7409,11 +7482,11 @@ react-refresh@^0.14.2: integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== react-remove-scroll-bar@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" - integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== + version "2.3.8" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" + integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== dependencies: - react-style-singleton "^2.2.1" + react-style-singleton "^2.2.2" tslib "^2.0.0" react-remove-scroll@2.6.0: @@ -7455,13 +7528,12 @@ react-router@5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-style-singleton@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" - integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== +react-style-singleton@^2.2.1, react-style-singleton@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz#4265608be69a4d70cfe3047f2c6c88b2c3ace388" + integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== dependencies: get-nonce "^1.0.0" - invariant "^2.2.4" tslib "^2.0.0" react-use-clipboard@^1.0.7: @@ -7753,30 +7825,31 @@ rollup@^4.20.0: fsevents "~2.3.2" rollup@^4.23.0: - version "4.28.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.28.0.tgz#eb8d28ed43ef60a18f21d0734d230ee79dd0de77" - integrity sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ== + version "4.28.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.28.1.tgz#7718ba34d62b449dfc49adbfd2f312b4fe0df4de" + integrity sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg== dependencies: "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.28.0" - "@rollup/rollup-android-arm64" "4.28.0" - "@rollup/rollup-darwin-arm64" "4.28.0" - "@rollup/rollup-darwin-x64" "4.28.0" - "@rollup/rollup-freebsd-arm64" "4.28.0" - "@rollup/rollup-freebsd-x64" "4.28.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.28.0" - "@rollup/rollup-linux-arm-musleabihf" "4.28.0" - "@rollup/rollup-linux-arm64-gnu" "4.28.0" - "@rollup/rollup-linux-arm64-musl" "4.28.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.28.0" - "@rollup/rollup-linux-riscv64-gnu" "4.28.0" - "@rollup/rollup-linux-s390x-gnu" "4.28.0" - "@rollup/rollup-linux-x64-gnu" "4.28.0" - "@rollup/rollup-linux-x64-musl" "4.28.0" - "@rollup/rollup-win32-arm64-msvc" "4.28.0" - "@rollup/rollup-win32-ia32-msvc" "4.28.0" - "@rollup/rollup-win32-x64-msvc" "4.28.0" + "@rollup/rollup-android-arm-eabi" "4.28.1" + "@rollup/rollup-android-arm64" "4.28.1" + "@rollup/rollup-darwin-arm64" "4.28.1" + "@rollup/rollup-darwin-x64" "4.28.1" + "@rollup/rollup-freebsd-arm64" "4.28.1" + "@rollup/rollup-freebsd-x64" "4.28.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.28.1" + "@rollup/rollup-linux-arm-musleabihf" "4.28.1" + "@rollup/rollup-linux-arm64-gnu" "4.28.1" + "@rollup/rollup-linux-arm64-musl" "4.28.1" + "@rollup/rollup-linux-loongarch64-gnu" "4.28.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.28.1" + "@rollup/rollup-linux-riscv64-gnu" "4.28.1" + "@rollup/rollup-linux-s390x-gnu" "4.28.1" + "@rollup/rollup-linux-x64-gnu" "4.28.1" + "@rollup/rollup-linux-x64-musl" "4.28.1" + "@rollup/rollup-win32-arm64-msvc" "4.28.1" + "@rollup/rollup-win32-ia32-msvc" "4.28.1" + "@rollup/rollup-win32-x64-msvc" "4.28.1" fsevents "~2.3.2" rrweb-cssom@^0.7.1: @@ -7856,9 +7929,9 @@ safe-regex-test@^1.0.3: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass@^1.42.1: - version "1.82.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.82.0.tgz#30da277af3d0fa6042e9ceabd0d984ed6d07df70" - integrity sha512-j4GMCTa8elGyN9A7x7bEglx0VgSpNUG4W4wNedQ33wSMdnkqQCT8HTwOaVSV4e6yQovcu/3Oc4coJP/l0xhL2Q== + version "1.83.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.0.tgz#e36842c0b88a94ed336fd16249b878a0541d536f" + integrity sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw== dependencies: chokidar "^4.0.0" immutable "^5.0.2" @@ -8654,9 +8727,9 @@ use-callback-ref@^1.3.0: tslib "^2.0.0" use-sidecar@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" - integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb" + integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== dependencies: detect-node-es "^1.1.0" tslib "^2.0.0" @@ -8801,9 +8874,9 @@ vite@^5.0.0: fsevents "~2.3.3" vite@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.2.tgz#7a22630c73c7b663335ddcdb2390971ffbc14993" - integrity sha512-XdQ+VsY2tJpBsKGs0wf3U/+azx8BBpYRHFAyKm5VeEZNOJZRB63q7Sc8Iup3k0TrN3KO6QgyzFf+opSbfY1y0g== + version "6.0.3" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.3.tgz#cc01f403e326a9fc1e064235df8a6de084c8a491" + integrity sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw== dependencies: esbuild "^0.24.0" postcss "^8.4.49" @@ -9145,6 +9218,6 @@ zod-validation-error@^3.0.3: integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ== zod@^3.22.4: - version "3.24.0" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.0.tgz#babb32313f7c5f4a99812feee806d186b4f76bde" - integrity sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w== + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" + integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== From ed269e0711e3e96f12e10bb900dbe24cd6833df8 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:12:12 +0100 Subject: [PATCH 57/72] Revert "Wait for .well-known/matrix/client to load before determining MatrixRTC foci" (#2902) This reverts commit 92afd5d63ae2914f651a3eef93554b3cbf4abb40. --- src/rtcSessionHelper.test.ts | 2 +- src/rtcSessionHelpers.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rtcSessionHelper.test.ts b/src/rtcSessionHelper.test.ts index 729545f7..7df9f1b3 100644 --- a/src/rtcSessionHelper.test.ts +++ b/src/rtcSessionHelper.test.ts @@ -40,7 +40,7 @@ test("It joins the correct Session", async () => { room: { roomId: "roomId", client: { - waitForClientWellKnown: vi.fn().mockResolvedValue(clientWellKnown), + getClientWellKnown: vi.fn().mockReturnValue(clientWellKnown), }, }, memberships: [], diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index 680df571..f1c7eb8c 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -44,9 +44,8 @@ async function makePreferredLivekitFoci( } // Prioritize the client well known over the configured sfu. - const wellKnownFoci = ( - await rtcSession.room.client.waitForClientWellKnown() - )?.[FOCI_WK_KEY]; + const wellKnownFoci = + rtcSession.room.client.getClientWellKnown()?.[FOCI_WK_KEY]; if (Array.isArray(wellKnownFoci)) { preferredFoci.push( ...wellKnownFoci From 79c40f198cb662481df012594f8fdbbb67be63bd Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 17 Dec 2024 04:01:56 +0000 Subject: [PATCH 58/72] Use finnish notation for observables (#2905) To help make our usage of the observables more readable/intuitive. --- .eslintrc.cjs | 1 + src/analytics/PosthogAnalytics.ts | 2 +- src/controls.ts | 12 +- src/grid/CallLayout.ts | 6 +- src/grid/Grid.tsx | 4 +- src/grid/GridLayout.tsx | 10 +- src/grid/OneOnOneLayout.tsx | 10 +- src/grid/SpotlightExpandedLayout.tsx | 6 +- src/grid/SpotlightLandscapeLayout.tsx | 8 +- src/grid/SpotlightPortraitLayout.tsx | 6 +- src/livekit/MediaDevicesContext.tsx | 10 +- src/room/CallEventAudioRenderer.test.tsx | 28 +- src/room/CallEventAudioRenderer.tsx | 4 +- src/room/InCallView.tsx | 48 +-- src/room/LobbyView.tsx | 2 +- src/room/useSwitchCamera.ts | 24 +- src/settings/settings.ts | 12 +- src/state/CallViewModel.test.ts | 58 ++-- src/state/CallViewModel.ts | 404 ++++++++++++----------- src/state/MediaViewModel.test.ts | 8 +- src/state/MediaViewModel.ts | 108 +++--- src/state/ObservableScope.ts | 12 +- src/state/TileStore.ts | 35 +- src/state/TileViewModel.ts | 6 +- src/state/observeSpeaker.test.ts | 65 ++-- src/state/observeSpeaker.ts | 10 +- src/tile/GridTile.tsx | 24 +- src/tile/SpotlightTile.tsx | 32 +- src/utils/observable.ts | 8 +- src/utils/test.ts | 18 +- 30 files changed, 491 insertions(+), 490 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index db5f3fd9..f338dac7 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -44,6 +44,7 @@ module.exports = { ], // To encourage good usage of RxJS: "rxjs/no-exposed-subjects": "error", + "rxjs/finnish": "error", }, settings: { react: { diff --git a/src/analytics/PosthogAnalytics.ts b/src/analytics/PosthogAnalytics.ts index 5124e711..2817f78c 100644 --- a/src/analytics/PosthogAnalytics.ts +++ b/src/analytics/PosthogAnalytics.ts @@ -415,7 +415,7 @@ export class PosthogAnalytics { // * When the user changes their preferences on this device // Note that for new accounts, pseudonymousAnalyticsOptIn won't be set, so updateAnonymityFromSettings // won't be called (i.e. this.anonymity will be left as the default, until the setting changes) - optInAnalytics.value.subscribe((optIn) => { + optInAnalytics.value$.subscribe((optIn) => { this.setAnonymity(optIn ? Anonymity.Pseudonymous : Anonymity.Disabled); this.maybeIdentifyUser().catch(() => logger.log("Could not identify user"), diff --git a/src/controls.ts b/src/controls.ts index 2e8ea7d3..fe8cc4de 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -13,18 +13,18 @@ export interface Controls { disablePip: () => void; } -export const setPipEnabled = new Subject(); +export const setPipEnabled$ = new Subject(); window.controls = { canEnterPip(): boolean { - return setPipEnabled.observed; + return setPipEnabled$.observed; }, enablePip(): void { - if (!setPipEnabled.observed) throw new Error("No call is running"); - setPipEnabled.next(true); + if (!setPipEnabled$.observed) throw new Error("No call is running"); + setPipEnabled$.next(true); }, disablePip(): void { - if (!setPipEnabled.observed) throw new Error("No call is running"); - setPipEnabled.next(false); + if (!setPipEnabled$.observed) throw new Error("No call is running"); + setPipEnabled$.next(false); }, }; diff --git a/src/grid/CallLayout.ts b/src/grid/CallLayout.ts index e05cd025..0e64481a 100644 --- a/src/grid/CallLayout.ts +++ b/src/grid/CallLayout.ts @@ -31,15 +31,15 @@ export interface CallLayoutInputs { /** * The minimum bounds of the layout area. */ - minBounds: Observable; + minBounds$: Observable; /** * The alignment of the floating spotlight tile, if present. */ - spotlightAlignment: BehaviorSubject; + spotlightAlignment$: BehaviorSubject; /** * The alignment of the small picture-in-picture tile, if present. */ - pipAlignment: BehaviorSubject; + pipAlignment$: BehaviorSubject; } export interface CallLayoutOutputs { diff --git a/src/grid/Grid.tsx b/src/grid/Grid.tsx index 268d4352..031a73b5 100644 --- a/src/grid/Grid.tsx +++ b/src/grid/Grid.tsx @@ -156,7 +156,7 @@ export function useVisibleTiles(callback: VisibleTilesCallback): void { ); } -const windowHeightObservable = fromEvent(window, "resize").pipe( +const windowHeightObservable$ = fromEvent(window, "resize").pipe( startWith(null), map(() => window.innerHeight), ); @@ -262,7 +262,7 @@ export function Grid< const [gridRoot, gridRef2] = useState(null); const gridRef = useMergedRefs(gridRef1, gridRef2); - const windowHeight = useObservableEagerState(windowHeightObservable); + const windowHeight = useObservableEagerState(windowHeightObservable$); const [layoutRoot, setLayoutRoot] = useState(null); const [generation, setGeneration] = useState(null); const [visibleTilesCallback, setVisibleTilesCallback] = diff --git a/src/grid/GridLayout.tsx b/src/grid/GridLayout.tsx index f4a29379..fd26c6ee 100644 --- a/src/grid/GridLayout.tsx +++ b/src/grid/GridLayout.tsx @@ -26,8 +26,8 @@ interface GridCSSProperties extends CSSProperties { * together in a scrolling grid. */ export const makeGridLayout: CallLayout = ({ - minBounds, - spotlightAlignment, + minBounds$, + spotlightAlignment$, }) => ({ scrollingOnTop: false, @@ -37,7 +37,7 @@ export const makeGridLayout: CallLayout = ({ useUpdateLayout(); const alignment = useObservableEagerState( useInitial(() => - spotlightAlignment.pipe( + spotlightAlignment$.pipe( distinctUntilChanged( (a1, a2) => a1.block === a2.block && a1.inline === a2.inline, ), @@ -47,7 +47,7 @@ export const makeGridLayout: CallLayout = ({ const onDragSpotlight: DragCallback = useCallback( ({ xRatio, yRatio }) => - spotlightAlignment.next({ + spotlightAlignment$.next({ block: yRatio < 0.5 ? "start" : "end", inline: xRatio < 0.5 ? "start" : "end", }), @@ -74,7 +74,7 @@ export const makeGridLayout: CallLayout = ({ scrolling: forwardRef(function GridLayout({ model, Slot }, ref) { useUpdateLayout(); useVisibleTiles(model.setVisibleTiles); - const { width, height: minHeight } = useObservableEagerState(minBounds); + const { width, height: minHeight } = useObservableEagerState(minBounds$); const { gap, tileWidth, tileHeight } = useMemo( () => arrangeTiles(width, minHeight, model.grid.length), [width, minHeight, model.grid.length], diff --git a/src/grid/OneOnOneLayout.tsx b/src/grid/OneOnOneLayout.tsx index fb0af714..9d49ae90 100644 --- a/src/grid/OneOnOneLayout.tsx +++ b/src/grid/OneOnOneLayout.tsx @@ -19,8 +19,8 @@ import { type DragCallback, useUpdateLayout } from "./Grid"; * is shown at maximum size, overlaid by a small view of the local participant. */ export const makeOneOnOneLayout: CallLayout = ({ - minBounds, - pipAlignment, + minBounds$, + pipAlignment$, }) => ({ scrollingOnTop: false, @@ -31,8 +31,8 @@ export const makeOneOnOneLayout: CallLayout = ({ scrolling: forwardRef(function OneOnOneLayoutScrolling({ model, Slot }, ref) { useUpdateLayout(); - const { width, height } = useObservableEagerState(minBounds); - const pipAlignmentValue = useObservableEagerState(pipAlignment); + const { width, height } = useObservableEagerState(minBounds$); + const pipAlignmentValue = useObservableEagerState(pipAlignment$); const { tileWidth, tileHeight } = useMemo( () => arrangeTiles(width, height, 1), [width, height], @@ -40,7 +40,7 @@ export const makeOneOnOneLayout: CallLayout = ({ const onDragLocalTile: DragCallback = useCallback( ({ xRatio, yRatio }) => - pipAlignment.next({ + pipAlignment$.next({ block: yRatio < 0.5 ? "start" : "end", inline: xRatio < 0.5 ? "start" : "end", }), diff --git a/src/grid/SpotlightExpandedLayout.tsx b/src/grid/SpotlightExpandedLayout.tsx index d4ce9af3..a50cecb9 100644 --- a/src/grid/SpotlightExpandedLayout.tsx +++ b/src/grid/SpotlightExpandedLayout.tsx @@ -19,7 +19,7 @@ import styles from "./SpotlightExpandedLayout.module.css"; */ export const makeSpotlightExpandedLayout: CallLayout< SpotlightExpandedLayoutModel -> = ({ pipAlignment }) => ({ +> = ({ pipAlignment$ }) => ({ scrollingOnTop: true, fixed: forwardRef(function SpotlightExpandedLayoutFixed( @@ -44,11 +44,11 @@ export const makeSpotlightExpandedLayout: CallLayout< ref, ) { useUpdateLayout(); - const pipAlignmentValue = useObservableEagerState(pipAlignment); + const pipAlignmentValue = useObservableEagerState(pipAlignment$); const onDragPip: DragCallback = useCallback( ({ xRatio, yRatio }) => - pipAlignment.next({ + pipAlignment$.next({ block: yRatio < 0.5 ? "start" : "end", inline: xRatio < 0.5 ? "start" : "end", }), diff --git a/src/grid/SpotlightLandscapeLayout.tsx b/src/grid/SpotlightLandscapeLayout.tsx index 3b80a166..a80cb6fa 100644 --- a/src/grid/SpotlightLandscapeLayout.tsx +++ b/src/grid/SpotlightLandscapeLayout.tsx @@ -21,7 +21,7 @@ import { useUpdateLayout, useVisibleTiles } from "./Grid"; */ export const makeSpotlightLandscapeLayout: CallLayout< SpotlightLandscapeLayoutModel -> = ({ minBounds }) => ({ +> = ({ minBounds$ }) => ({ scrollingOnTop: false, fixed: forwardRef(function SpotlightLandscapeLayoutFixed( @@ -29,7 +29,7 @@ export const makeSpotlightLandscapeLayout: CallLayout< ref, ) { useUpdateLayout(); - useObservableEagerState(minBounds); + useObservableEagerState(minBounds$); return (
@@ -51,9 +51,9 @@ export const makeSpotlightLandscapeLayout: CallLayout< ) { useUpdateLayout(); useVisibleTiles(model.setVisibleTiles); - useObservableEagerState(minBounds); + useObservableEagerState(minBounds$); const withIndicators = - useObservableEagerState(model.spotlight.media).length > 1; + useObservableEagerState(model.spotlight.media$).length > 1; return (
diff --git a/src/grid/SpotlightPortraitLayout.tsx b/src/grid/SpotlightPortraitLayout.tsx index 6af0fa39..9f62a520 100644 --- a/src/grid/SpotlightPortraitLayout.tsx +++ b/src/grid/SpotlightPortraitLayout.tsx @@ -27,7 +27,7 @@ interface GridCSSProperties extends CSSProperties { */ export const makeSpotlightPortraitLayout: CallLayout< SpotlightPortraitLayoutModel -> = ({ minBounds }) => ({ +> = ({ minBounds$ }) => ({ scrollingOnTop: false, fixed: forwardRef(function SpotlightPortraitLayoutFixed( @@ -55,7 +55,7 @@ export const makeSpotlightPortraitLayout: CallLayout< ) { useUpdateLayout(); useVisibleTiles(model.setVisibleTiles); - const { width } = useObservableEagerState(minBounds); + const { width } = useObservableEagerState(minBounds$); const { gap, tileWidth, tileHeight } = arrangeTiles( width, // TODO: We pretend that the minimum height is the width, because the @@ -64,7 +64,7 @@ export const makeSpotlightPortraitLayout: CallLayout< model.grid.length, ); const withIndicators = - useObservableEagerState(model.spotlight.media).length > 1; + useObservableEagerState(model.spotlight.media$).length > 1; return (
createMediaDeviceObserver( kind, @@ -86,7 +86,7 @@ function useMediaDevice( const available = useObservableEagerState( useMemo( () => - deviceObserver.pipe( + deviceObserver$.pipe( map((availableRaw) => { // Sometimes browsers (particularly Firefox) can return multiple device // entries for the exact same device ID; using a map deduplicates them @@ -117,7 +117,7 @@ function useMediaDevice( return available; }), ), - [kind, deviceObserver], + [kind, deviceObserver$], ), ); @@ -140,13 +140,13 @@ function useMediaDevice( const selectedGroupId = useObservableEagerState( useMemo( () => - deviceObserver.pipe( + deviceObserver$.pipe( map( (availableRaw) => availableRaw.find((d) => d.deviceId === selectedId)?.groupId, ), ), - [deviceObserver, selectedId], + [deviceObserver$, selectedId], ), ); diff --git a/src/room/CallEventAudioRenderer.test.tsx b/src/room/CallEventAudioRenderer.test.tsx index e8d22704..6868de49 100644 --- a/src/room/CallEventAudioRenderer.test.tsx +++ b/src/room/CallEventAudioRenderer.test.tsx @@ -100,13 +100,13 @@ function getMockEnv( ): { vm: CallViewModel; session: MockRTCSession; - remoteRtcMemberships: BehaviorSubject; + remoteRtcMemberships$: BehaviorSubject; } { const matrixRoomMembers = new Map(members.map((p) => [p.userId, p])); - const remoteParticipants = of([aliceParticipant]); + const remoteParticipants$ = of([aliceParticipant]); const liveKitRoom = mockLivekitRoom( { localParticipant }, - { remoteParticipants }, + { remoteParticipants$ }, ); const matrixRoom = mockMatrixRoom({ client: { @@ -118,14 +118,14 @@ function getMockEnv( getMember: (userId) => matrixRoomMembers.get(userId) ?? null, }); - const remoteRtcMemberships = new BehaviorSubject( + const remoteRtcMemberships$ = new BehaviorSubject( initialRemoteRtcMemberships, ); const session = new MockRTCSession( matrixRoom, localRtcMember, - ).withMemberships(remoteRtcMemberships); + ).withMemberships(remoteRtcMemberships$); const vm = new CallViewModel( session as unknown as MatrixRTCSession, @@ -135,7 +135,7 @@ function getMockEnv( }, of(ConnectionState.Connected), ); - return { vm, session, remoteRtcMemberships }; + return { vm, session, remoteRtcMemberships$ }; } /** @@ -146,33 +146,33 @@ function getMockEnv( * a noise every time. */ test("plays one sound when entering a call", () => { - const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]); + const { session, vm, remoteRtcMemberships$ } = getMockEnv([local, alice]); render(); // Joining a call usually means remote participants are added later. act(() => { - remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]); + remoteRtcMemberships$.next([aliceRtcMember, bobRtcMember]); }); expect(playSound).toHaveBeenCalledOnce(); }); // TODO: Same test? test("plays a sound when a user joins", () => { - const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]); + const { session, vm, remoteRtcMemberships$ } = getMockEnv([local, alice]); render(); act(() => { - remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]); + remoteRtcMemberships$.next([aliceRtcMember, bobRtcMember]); }); // Play a sound when joining a call. expect(playSound).toBeCalledWith("join"); }); test("plays a sound when a user leaves", () => { - const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]); + const { session, vm, remoteRtcMemberships$ } = getMockEnv([local, alice]); render(); act(() => { - remoteRtcMemberships.next([]); + remoteRtcMemberships$.next([]); }); expect(playSound).toBeCalledWith("left"); }); @@ -185,7 +185,7 @@ test("plays no sound when the participant list is more than the maximum size", ( ); } - const { session, vm, remoteRtcMemberships } = getMockEnv( + const { session, vm, remoteRtcMemberships$ } = getMockEnv( [local, alice], mockRtcMemberships, ); @@ -193,7 +193,7 @@ test("plays no sound when the participant list is more than the maximum size", ( render(); expect(playSound).not.toBeCalled(); act(() => { - remoteRtcMemberships.next( + remoteRtcMemberships$.next( mockRtcMemberships.slice(0, MAX_PARTICIPANT_COUNT_FOR_SOUND - 1), ); }); diff --git a/src/room/CallEventAudioRenderer.tsx b/src/room/CallEventAudioRenderer.tsx index a363c6f5..a2a0a7f1 100644 --- a/src/room/CallEventAudioRenderer.tsx +++ b/src/room/CallEventAudioRenderer.tsx @@ -65,7 +65,7 @@ export function CallEventAudioRenderer({ }, [audioEngineRef, previousRaisedHandCount, raisedHandCount]); useEffect(() => { - const joinSub = vm.memberChanges + const joinSub = vm.memberChanges$ .pipe( filter( ({ joined, ids }) => @@ -77,7 +77,7 @@ export function CallEventAudioRenderer({ void audioEngineRef.current?.playSound("join"); }); - const leftSub = vm.memberChanges + const leftSub = vm.memberChanges$ .pipe( filter( ({ ids, left }) => diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 976e4e94..dde64104 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -110,8 +110,8 @@ export const ActiveCall: FC = (props) => { sfuConfig, props.e2eeSystem, ); - const connStateObservable = useObservable( - (inputs) => inputs.pipe(map(([connState]) => connState)), + const connStateObservable$ = useObservable( + (inputs$) => inputs$.pipe(map(([connState]) => connState)), [connState], ); const [vm, setVm] = useState(null); @@ -131,12 +131,12 @@ export const ActiveCall: FC = (props) => { props.rtcSession, livekitRoom, props.e2eeSystem, - connStateObservable, + connStateObservable$, ); setVm(vm); return (): void => vm.destroy(); } - }, [props.rtcSession, livekitRoom, props.e2eeSystem, connStateObservable]); + }, [props.rtcSession, livekitRoom, props.e2eeSystem, connStateObservable$]); if (livekitRoom === undefined || vm === null) return null; @@ -225,14 +225,14 @@ export const InCallView: FC = ({ () => void toggleRaisedHand(), ); - const windowMode = useObservableEagerState(vm.windowMode); - const layout = useObservableEagerState(vm.layout); - const tileStoreGeneration = useObservableEagerState(vm.tileStoreGeneration); + const windowMode = useObservableEagerState(vm.windowMode$); + const layout = useObservableEagerState(vm.layout$); + const tileStoreGeneration = useObservableEagerState(vm.tileStoreGeneration$); const [debugTileLayout] = useSetting(debugTileLayoutSetting); - const gridMode = useObservableEagerState(vm.gridMode); - const showHeader = useObservableEagerState(vm.showHeader); - const showFooter = useObservableEagerState(vm.showFooter); - const switchCamera = useSwitchCamera(vm.localVideo); + const gridMode = useObservableEagerState(vm.gridMode$); + const showHeader = useObservableEagerState(vm.showHeader$); + const showFooter = useObservableEagerState(vm.showFooter$); + const switchCamera = useSwitchCamera(vm.localVideo$); // Ideally we could detect taps by listening for click events and checking // that the pointerType of the event is "touch", but this isn't yet supported @@ -317,15 +317,15 @@ export const InCallView: FC = ({ windowMode, ], ); - const gridBoundsObservable = useObservable( - (inputs) => inputs.pipe(map(([gridBounds]) => gridBounds)), + const gridBoundsObservable$ = useObservable( + (inputs$) => inputs$.pipe(map(([gridBounds]) => gridBounds)), [gridBounds], ); - const spotlightAlignment = useInitial( + const spotlightAlignment$ = useInitial( () => new BehaviorSubject(defaultSpotlightAlignment), ); - const pipAlignment = useInitial( + const pipAlignment$ = useInitial( () => new BehaviorSubject(defaultPipAlignment), ); @@ -383,15 +383,17 @@ export const InCallView: FC = ({ { className, style, targetWidth, targetHeight, model }, ref, ) { - const spotlightExpanded = useObservableEagerState(vm.spotlightExpanded); + const spotlightExpanded = useObservableEagerState( + vm.spotlightExpanded$, + ); const onToggleExpanded = useObservableEagerState( - vm.toggleSpotlightExpanded, + vm.toggleSpotlightExpanded$, ); const showSpeakingIndicatorsValue = useObservableEagerState( - vm.showSpeakingIndicators, + vm.showSpeakingIndicators$, ); const showSpotlightIndicatorsValue = useObservableEagerState( - vm.showSpotlightIndicators, + vm.showSpotlightIndicators$, ); return model instanceof GridTileViewModel ? ( @@ -424,9 +426,9 @@ export const InCallView: FC = ({ const layouts = useMemo(() => { const inputs = { - minBounds: gridBoundsObservable, - spotlightAlignment, - pipAlignment, + minBounds$: gridBoundsObservable$, + spotlightAlignment$, + pipAlignment$, }; return { grid: makeGridLayout(inputs), @@ -435,7 +437,7 @@ export const InCallView: FC = ({ "spotlight-expanded": makeSpotlightExpandedLayout(inputs), "one-on-one": makeOneOnOneLayout(inputs), }; - }, [gridBoundsObservable, spotlightAlignment, pipAlignment]); + }, [gridBoundsObservable$, spotlightAlignment$, pipAlignment$]); const renderContent = (): JSX.Element => { if (layout.type === "pip") { diff --git a/src/room/LobbyView.tsx b/src/room/LobbyView.tsx index e7dfe3c5..4622cd3e 100644 --- a/src/room/LobbyView.tsx +++ b/src/room/LobbyView.tsx @@ -148,7 +148,7 @@ export const LobbyView: FC = ({ const switchCamera = useSwitchCamera( useObservable( - (inputs) => inputs.pipe(map(([video]) => video)), + (inputs$) => inputs$.pipe(map(([video]) => video)), [videoTrack], ), ); diff --git a/src/room/useSwitchCamera.ts b/src/room/useSwitchCamera.ts index 8bbfc92c..1cf5d29d 100644 --- a/src/room/useSwitchCamera.ts +++ b/src/room/useSwitchCamera.ts @@ -31,17 +31,17 @@ import { useLatest } from "../useLatest"; * producing a callback if so. */ export function useSwitchCamera( - video: Observable, + video$: Observable, ): (() => void) | null { const mediaDevices = useMediaDevices(); const setVideoInput = useLatest(mediaDevices.videoInput.select); // Produce an observable like the input 'video' observable, except make it // emit whenever the track is muted or the device changes - const videoTrack: Observable = useObservable( - (inputs) => - inputs.pipe( - switchMap(([video]) => video), + const videoTrack$: Observable = useObservable( + (inputs$) => + inputs$.pipe( + switchMap(([video$]) => video$), switchMap((video) => { if (video === null) return of(null); return merge( @@ -53,15 +53,15 @@ export function useSwitchCamera( ); }), ), - [video], + [video$], ); - const switchCamera: Observable<(() => void) | null> = useObservable( - (inputs) => + const switchCamera$: Observable<(() => void) | null> = useObservable( + (inputs$) => platform === "desktop" ? of(null) - : inputs.pipe( - switchMap(([track]) => track), + : inputs$.pipe( + switchMap(([track$]) => track$), map((track) => { if (track === null) return null; const facingMode = facingModeFromLocalTrack(track).facingMode; @@ -86,8 +86,8 @@ export function useSwitchCamera( ); }), ), - [videoTrack], + [videoTrack$], ); - return useObservableEagerState(switchCamera); + return useObservableEagerState(switchCamera$); } diff --git a/src/settings/settings.ts b/src/settings/settings.ts index a902f9ab..ebb5dffc 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -31,17 +31,17 @@ export class Setting { } } - this._value = new BehaviorSubject(initialValue); - this.value = this._value; + this._value$ = new BehaviorSubject(initialValue); + this.value$ = this._value$; } private readonly key: string; - private readonly _value: BehaviorSubject; - public readonly value: Observable; + private readonly _value$: BehaviorSubject; + public readonly value$: Observable; public readonly setValue = (value: T): void => { - this._value.next(value); + this._value$.next(value); localStorage.setItem(this.key, JSON.stringify(value)); }; } @@ -50,7 +50,7 @@ export class Setting { * React hook that returns a settings's current value and a setter. */ export function useSetting(setting: Setting): [T, (value: T) => void] { - return [useObservableEagerState(setting.value), setting.setValue]; + return [useObservableEagerState(setting.value$), setting.setValue]; } // null = undecided diff --git a/src/state/CallViewModel.test.ts b/src/state/CallViewModel.test.ts index d5b84d49..c9072006 100644 --- a/src/state/CallViewModel.test.ts +++ b/src/state/CallViewModel.test.ts @@ -124,15 +124,15 @@ export type LayoutSummary = | OneOnOneLayoutSummary | PipLayoutSummary; -function summarizeLayout(l: Observable): Observable { - return l.pipe( +function summarizeLayout$(l$: Observable): Observable { + return l$.pipe( switchMap((l) => { switch (l.type) { case "grid": return combineLatest( [ - l.spotlight?.media ?? of(undefined), - ...l.grid.map((vm) => vm.media), + l.spotlight?.media$ ?? of(undefined), + ...l.grid.map((vm) => vm.media$), ], (spotlight, ...grid) => ({ type: l.type, @@ -143,7 +143,7 @@ function summarizeLayout(l: Observable): Observable { case "spotlight-landscape": case "spotlight-portrait": return combineLatest( - [l.spotlight.media, ...l.grid.map((vm) => vm.media)], + [l.spotlight.media$, ...l.grid.map((vm) => vm.media$)], (spotlight, ...grid) => ({ type: l.type, spotlight: spotlight.map((vm) => vm.id), @@ -152,7 +152,7 @@ function summarizeLayout(l: Observable): Observable { ); case "spotlight-expanded": return combineLatest( - [l.spotlight.media, l.pip?.media ?? of(undefined)], + [l.spotlight.media$, l.pip?.media$ ?? of(undefined)], (spotlight, pip) => ({ type: l.type, spotlight: spotlight.map((vm) => vm.id), @@ -161,7 +161,7 @@ function summarizeLayout(l: Observable): Observable { ); case "one-on-one": return combineLatest( - [l.local.media, l.remote.media], + [l.local.media$, l.remote.media$], (local, remote) => ({ type: l.type, local: local.id, @@ -169,7 +169,7 @@ function summarizeLayout(l: Observable): Observable { }), ); case "pip": - return l.spotlight.media.pipe( + return l.spotlight.media$.pipe( map((spotlight) => ({ type: l.type, spotlight: spotlight.map((vm) => vm.id), @@ -186,9 +186,9 @@ function summarizeLayout(l: Observable): Observable { } function withCallViewModel( - remoteParticipants: Observable, - rtcMembers: Observable[]>, - connectionState: Observable, + remoteParticipants$: Observable, + rtcMembers$: Observable[]>, + connectionState$: Observable, speaking: Map>, continuation: (vm: CallViewModel) => void, ): void { @@ -203,10 +203,10 @@ function withCallViewModel( room, localRtcMember, [], - ).withMemberships(rtcMembers); + ).withMemberships(rtcMembers$); const participantsSpy = vi .spyOn(ComponentsCore, "connectedParticipantsObserver") - .mockReturnValue(remoteParticipants); + .mockReturnValue(remoteParticipants$); const mediaSpy = vi .spyOn(ComponentsCore, "observeParticipantMedia") .mockImplementation((p) => @@ -232,7 +232,7 @@ function withCallViewModel( const liveKitRoom = mockLivekitRoom( { localParticipant }, - { remoteParticipants }, + { remoteParticipants$ }, ); const vm = new CallViewModel( @@ -241,7 +241,7 @@ function withCallViewModel( { kind: E2eeType.PER_PARTICIPANT, }, - connectionState, + connectionState$, ); onTestFinished(() => { @@ -276,7 +276,7 @@ test("participants are retained during a focus switch", () => { }), new Map(), (vm) => { - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -320,7 +320,7 @@ test("screen sharing activates spotlight layout", () => { g: () => vm.setGridMode("grid"), }); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -363,7 +363,7 @@ test("screen sharing activates spotlight layout", () => { }, }, ); - expectObservable(vm.showSpeakingIndicators).toBe( + expectObservable(vm.showSpeakingIndicators$).toBe( expectedShowSpeakingMarbles, { y: true, @@ -402,13 +402,13 @@ test("participants stay in the same order unless to appear/disappear", () => { a: () => { // We imagine that only three tiles (the first three) will be visible // on screen at a time - vm.layout.subscribe((layout) => { + vm.layout$.subscribe((layout) => { if (layout.type === "grid") layout.setVisibleTiles(3); }); }, }); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -455,7 +455,7 @@ test("participants adjust order when space becomes constrained", () => { ]), (vm) => { let setVisibleTiles: ((value: number) => void) | null = null; - vm.layout.subscribe((layout) => { + vm.layout$.subscribe((layout) => { if (layout.type === "grid") setVisibleTiles = layout.setVisibleTiles; }); schedule(visibilityInputMarbles, { @@ -463,7 +463,7 @@ test("participants adjust order when space becomes constrained", () => { b: () => setVisibleTiles!(3), }); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -509,7 +509,7 @@ test("spotlight speakers swap places", () => { (vm) => { schedule(modeInputMarbles, { s: () => vm.setGridMode("spotlight") }); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -557,7 +557,7 @@ test("layout enters picture-in-picture mode when requested", () => { d: () => window.controls.disablePip(), }); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -600,12 +600,12 @@ test("spotlight remembers whether it's expanded", () => { schedule(expandInputMarbles, { a: () => { let toggle: () => void; - vm.toggleSpotlightExpanded.subscribe((val) => (toggle = val!)); + vm.toggleSpotlightExpanded$.subscribe((val) => (toggle = val!)); toggle!(); }, }); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -662,7 +662,7 @@ test("participants must have a MatrixRTCSession to be visible", () => { new Map(), (vm) => { vm.setGridMode("grid"); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -706,7 +706,7 @@ test("shows participants without MatrixRTCSession when enabled in settings", () new Map(), (vm) => { vm.setGridMode("grid"); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { @@ -753,7 +753,7 @@ it("should show at least one tile per MatrixRTCSession", () => { new Map(), (vm) => { vm.setGridMode("grid"); - expectObservable(summarizeLayout(vm.layout)).toBe( + expectObservable(summarizeLayout$(vm.layout$)).toBe( expectedLayoutMarbles, { a: { diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 8e080fdc..36cbbac8 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -62,7 +62,7 @@ import { import { LocalUserMediaViewModel, type MediaViewModel, - observeTrackReference, + observeTrackReference$, RemoteUserMediaViewModel, ScreenShareViewModel, type UserMediaViewModel, @@ -71,7 +71,7 @@ import { accumulate, finalizeValue } from "../utils/observable"; import { ObservableScope } from "./ObservableScope"; import { duplicateTiles, showNonMemberTiles } from "../settings/settings"; import { isFirefox } from "../Platform"; -import { setPipEnabled } from "../controls"; +import { setPipEnabled$ } from "../controls"; import { type GridTileViewModel, type SpotlightTileViewModel, @@ -82,7 +82,7 @@ import { spotlightExpandedLayout } from "./SpotlightExpandedLayout"; import { oneOnOneLayout } from "./OneOnOneLayout"; import { pipLayout } from "./PipLayout"; import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; -import { observeSpeaker } from "./observeSpeaker"; +import { observeSpeaker$ } from "./observeSpeaker"; import { shallowEquals } from "../utils/array"; // How long we wait after a focus switch before showing the real participant @@ -232,12 +232,12 @@ interface LayoutScanState { class UserMedia { private readonly scope = new ObservableScope(); public readonly vm: UserMediaViewModel; - private readonly participant: BehaviorSubject< + private readonly participant$: BehaviorSubject< LocalParticipant | RemoteParticipant | undefined >; - public readonly speaker: Observable; - public readonly presenter: Observable; + public readonly speaker$: Observable; + public readonly presenter$: Observable; public constructor( public readonly id: string, member: RoomMember | undefined, @@ -245,13 +245,13 @@ class UserMedia { encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, ) { - this.participant = new BehaviorSubject(participant); + this.participant$ = new BehaviorSubject(participant); if (participant?.isLocal) { this.vm = new LocalUserMediaViewModel( this.id, member, - this.participant.asObservable() as Observable, + this.participant$.asObservable() as Observable, encryptionSystem, livekitRoom, ); @@ -259,7 +259,7 @@ class UserMedia { this.vm = new RemoteUserMediaViewModel( id, member, - this.participant.asObservable() as Observable< + this.participant$.asObservable() as Observable< RemoteParticipant | undefined >, encryptionSystem, @@ -267,9 +267,9 @@ class UserMedia { ); } - this.speaker = observeSpeaker(this.vm.speaking).pipe(this.scope.state()); + this.speaker$ = observeSpeaker$(this.vm.speaking$).pipe(this.scope.state()); - this.presenter = this.participant.pipe( + this.presenter$ = this.participant$.pipe( switchMap( (p) => (p && @@ -289,9 +289,9 @@ class UserMedia { public updateParticipant( newParticipant: LocalParticipant | RemoteParticipant | undefined, ): void { - if (this.participant.value !== newParticipant) { + if (this.participant$.value !== newParticipant) { // Update the BehaviourSubject in the UserMedia. - this.participant.next(newParticipant); + this.participant$.next(newParticipant); } } @@ -303,7 +303,7 @@ class UserMedia { class ScreenShare { public readonly vm: ScreenShareViewModel; - private readonly participant: BehaviorSubject< + private readonly participant$: BehaviorSubject< LocalParticipant | RemoteParticipant >; @@ -314,12 +314,12 @@ class ScreenShare { encryptionSystem: EncryptionSystem, liveKitRoom: LivekitRoom, ) { - this.participant = new BehaviorSubject(participant); + this.participant$ = new BehaviorSubject(participant); this.vm = new ScreenShareViewModel( id, member, - this.participant.asObservable(), + this.participant$.asObservable(), encryptionSystem, liveKitRoom, participant.isLocal, @@ -357,8 +357,8 @@ function findMatrixRoomMember( // TODO: Move wayyyy more business logic from the call and lobby views into here export class CallViewModel extends ViewModel { - public readonly localVideo: Observable = - observeTrackReference( + public readonly localVideo$: Observable = + observeTrackReference$( of(this.livekitRoom.localParticipant), Track.Source.Camera, ).pipe( @@ -371,16 +371,16 @@ export class CallViewModel extends ViewModel { /** * The raw list of RemoteParticipants as reported by LiveKit */ - private readonly rawRemoteParticipants: Observable = + private readonly rawRemoteParticipants$: Observable = connectedParticipantsObserver(this.livekitRoom).pipe(this.scope.state()); /** * Lists of RemoteParticipants to "hold" on display, even if LiveKit claims that * they've left */ - private readonly remoteParticipantHolds: Observable = - this.connectionState.pipe( - withLatestFrom(this.rawRemoteParticipants), + private readonly remoteParticipantHolds$: Observable = + this.connectionState$.pipe( + withLatestFrom(this.rawRemoteParticipants$), mergeMap(([s, ps]) => { // Whenever we switch focuses, we should retain all the previous // participants for at least POST_FOCUS_PARTICIPANT_UPDATE_DELAY_MS ms to @@ -392,7 +392,7 @@ export class CallViewModel extends ViewModel { // Wait for time to pass and the connection state to have changed forkJoin([ timer(POST_FOCUS_PARTICIPANT_UPDATE_DELAY_MS), - this.connectionState.pipe( + this.connectionState$.pipe( filter((s) => s !== ECAddonConnectionState.ECSwitchingFocus), take(1), ), @@ -415,9 +415,9 @@ export class CallViewModel extends ViewModel { /** * The RemoteParticipants including those that are being "held" on the screen */ - private readonly remoteParticipants: Observable = + private readonly remoteParticipants$: Observable = combineLatest( - [this.rawRemoteParticipants, this.remoteParticipantHolds], + [this.rawRemoteParticipants$, this.remoteParticipantHolds$], (raw, holds) => { const result = [...raw]; const resultIds = new Set(result.map((p) => p.identity)); @@ -439,10 +439,10 @@ export class CallViewModel extends ViewModel { /** * List of MediaItems that we want to display */ - private readonly mediaItems: Observable = combineLatest([ - this.remoteParticipants, + private readonly mediaItems$: Observable = combineLatest([ + this.remoteParticipants$, observeParticipantMedia(this.livekitRoom.localParticipant), - duplicateTiles.value, + duplicateTiles.value$, // Also react to changes in the MatrixRTC session list. // The session list will also be update if a room membership changes. // No additional RoomState event listener needs to be set up. @@ -450,7 +450,7 @@ export class CallViewModel extends ViewModel { this.matrixRTCSession, MatrixRTCSessionEvent.MembershipsChanged, ).pipe(startWith(null)), - showNonMemberTiles.value, + showNonMemberTiles.value$, ]).pipe( scan( ( @@ -606,13 +606,13 @@ export class CallViewModel extends ViewModel { /** * List of MediaItems that we want to display, that are of type UserMedia */ - private readonly userMedia: Observable = this.mediaItems.pipe( + private readonly userMedia$: Observable = this.mediaItems$.pipe( map((mediaItems) => mediaItems.filter((m): m is UserMedia => m instanceof UserMedia), ), ); - public readonly memberChanges = this.userMedia + public readonly memberChanges$ = this.userMedia$ .pipe(map((mediaItems) => mediaItems.map((m) => m.id))) .pipe( scan( @@ -628,22 +628,22 @@ export class CallViewModel extends ViewModel { /** * List of MediaItems that we want to display, that are of type ScreenShare */ - private readonly screenShares: Observable = - this.mediaItems.pipe( + private readonly screenShares$: Observable = + this.mediaItems$.pipe( map((mediaItems) => mediaItems.filter((m): m is ScreenShare => m instanceof ScreenShare), ), this.scope.state(), ); - private readonly spotlightSpeaker: Observable = - this.userMedia.pipe( + private readonly spotlightSpeaker$: Observable = + this.userMedia$.pipe( switchMap((mediaItems) => mediaItems.length === 0 ? of([]) : combineLatest( mediaItems.map((m) => - m.vm.speaking.pipe(map((s) => [m, s] as const)), + m.vm.speaking$.pipe(map((s) => [m, s] as const)), ), ), ), @@ -672,52 +672,53 @@ export class CallViewModel extends ViewModel { this.scope.state(), ); - private readonly grid: Observable = this.userMedia.pipe( - switchMap((mediaItems) => { - const bins = mediaItems.map((m) => - combineLatest( - [ - m.speaker, - m.presenter, - m.vm.videoEnabled, - m.vm instanceof LocalUserMediaViewModel - ? m.vm.alwaysShow - : of(false), - ], - (speaker, presenter, video, alwaysShow) => { - let bin: SortingBin; - if (m.vm.local) - bin = alwaysShow - ? SortingBin.SelfAlwaysShown - : SortingBin.SelfNotAlwaysShown; - else if (presenter) bin = SortingBin.Presenters; - else if (speaker) bin = SortingBin.Speakers; - else if (video) bin = SortingBin.Video; - else bin = SortingBin.NoVideo; + private readonly grid$: Observable = + this.userMedia$.pipe( + switchMap((mediaItems) => { + const bins = mediaItems.map((m) => + combineLatest( + [ + m.speaker$, + m.presenter$, + m.vm.videoEnabled$, + m.vm instanceof LocalUserMediaViewModel + ? m.vm.alwaysShow$ + : of(false), + ], + (speaker, presenter, video, alwaysShow) => { + let bin: SortingBin; + if (m.vm.local) + bin = alwaysShow + ? SortingBin.SelfAlwaysShown + : SortingBin.SelfNotAlwaysShown; + else if (presenter) bin = SortingBin.Presenters; + else if (speaker) bin = SortingBin.Speakers; + else if (video) bin = SortingBin.Video; + else bin = SortingBin.NoVideo; - return [m, bin] as const; - }, - ), - ); - // Sort the media by bin order and generate a tile for each one - return bins.length === 0 - ? of([]) - : combineLatest(bins, (...bins) => - bins.sort(([, bin1], [, bin2]) => bin1 - bin2).map(([m]) => m.vm), - ); - }), - distinctUntilChanged(shallowEquals), - this.scope.state(), - ); + return [m, bin] as const; + }, + ), + ); + // Sort the media by bin order and generate a tile for each one + return bins.length === 0 + ? of([]) + : combineLatest(bins, (...bins) => + bins.sort(([, bin1], [, bin2]) => bin1 - bin2).map(([m]) => m.vm), + ); + }), + distinctUntilChanged(shallowEquals), + this.scope.state(), + ); - private readonly spotlight: Observable = - this.screenShares.pipe( + private readonly spotlight$: Observable = + this.screenShares$.pipe( switchMap((screenShares) => { if (screenShares.length > 0) { return of(screenShares.map((m) => m.vm)); } - return this.spotlightSpeaker.pipe( + return this.spotlightSpeaker$.pipe( map((speaker) => (speaker ? [speaker] : [])), ); }), @@ -725,14 +726,14 @@ export class CallViewModel extends ViewModel { this.scope.state(), ); - private readonly pip: Observable = combineLatest([ - this.screenShares, - this.spotlightSpeaker, - this.mediaItems, + private readonly pip$: Observable = combineLatest([ + this.screenShares$, + this.spotlightSpeaker$, + this.mediaItems$, ]).pipe( switchMap(([screenShares, spotlight, mediaItems]) => { if (screenShares.length > 0) { - return this.spotlightSpeaker; + return this.spotlightSpeaker$; } if (!spotlight || spotlight.local) { return of(null); @@ -749,7 +750,7 @@ export class CallViewModel extends ViewModel { if (!localUserMediaViewModel) { return of(null); } - return localUserMediaViewModel.alwaysShow.pipe( + return localUserMediaViewModel.alwaysShow$.pipe( map((alwaysShow) => { if (alwaysShow) { return localUserMediaViewModel; @@ -762,19 +763,19 @@ export class CallViewModel extends ViewModel { this.scope.state(), ); - private readonly hasRemoteScreenShares: Observable = - this.spotlight.pipe( + private readonly hasRemoteScreenShares$: Observable = + this.spotlight$.pipe( map((spotlight) => spotlight.some((vm) => !vm.local && vm instanceof ScreenShareViewModel), ), distinctUntilChanged(), ); - private readonly pipEnabled: Observable = setPipEnabled.pipe( + private readonly pipEnabled$: Observable = setPipEnabled$.pipe( startWith(false), ); - private readonly naturalWindowMode: Observable = fromEvent( + private readonly naturalWindowMode$: Observable = fromEvent( window, "resize", ).pipe( @@ -796,30 +797,30 @@ export class CallViewModel extends ViewModel { /** * The general shape of the window. */ - public readonly windowMode: Observable = this.pipEnabled.pipe( - switchMap((pip) => (pip ? of("pip") : this.naturalWindowMode)), + public readonly windowMode$: Observable = this.pipEnabled$.pipe( + switchMap((pip) => (pip ? of("pip") : this.naturalWindowMode$)), ); - private readonly spotlightExpandedToggle = new Subject(); - public readonly spotlightExpanded: Observable = - this.spotlightExpandedToggle.pipe( + private readonly spotlightExpandedToggle$ = new Subject(); + public readonly spotlightExpanded$: Observable = + this.spotlightExpandedToggle$.pipe( accumulate(false, (expanded) => !expanded), this.scope.state(), ); - private readonly gridModeUserSelection = new Subject(); + private readonly gridModeUserSelection$ = new Subject(); /** * The layout mode of the media tile grid. */ - public readonly gridMode: Observable = + public readonly gridMode$: Observable = // If the user hasn't selected spotlight and somebody starts screen sharing, // automatically switch to spotlight mode and reset when screen sharing ends - this.gridModeUserSelection.pipe( + this.gridModeUserSelection$.pipe( startWith(null), switchMap((userSelection) => (userSelection === "spotlight" ? EMPTY - : combineLatest([this.hasRemoteScreenShares, this.windowMode]).pipe( + : combineLatest([this.hasRemoteScreenShares$, this.windowMode$]).pipe( skip(userSelection === null ? 0 : 1), map( ([hasScreenShares, windowMode]): GridMode => @@ -834,43 +835,41 @@ export class CallViewModel extends ViewModel { ); public setGridMode(value: GridMode): void { - this.gridModeUserSelection.next(value); + this.gridModeUserSelection$.next(value); } - private readonly gridLayoutMedia: Observable = combineLatest( - [this.grid, this.spotlight], - (grid, spotlight) => ({ + private readonly gridLayoutMedia$: Observable = + combineLatest([this.grid$, this.spotlight$], (grid, spotlight) => ({ type: "grid", spotlight: spotlight.some((vm) => vm instanceof ScreenShareViewModel) ? spotlight : undefined, grid, - }), - ); + })); - private readonly spotlightLandscapeLayoutMedia: Observable = - combineLatest([this.grid, this.spotlight], (grid, spotlight) => ({ + private readonly spotlightLandscapeLayoutMedia$: Observable = + combineLatest([this.grid$, this.spotlight$], (grid, spotlight) => ({ type: "spotlight-landscape", spotlight, grid, })); - private readonly spotlightPortraitLayoutMedia: Observable = - combineLatest([this.grid, this.spotlight], (grid, spotlight) => ({ + private readonly spotlightPortraitLayoutMedia$: Observable = + combineLatest([this.grid$, this.spotlight$], (grid, spotlight) => ({ type: "spotlight-portrait", spotlight, grid, })); - private readonly spotlightExpandedLayoutMedia: Observable = - combineLatest([this.spotlight, this.pip], (spotlight, pip) => ({ + private readonly spotlightExpandedLayoutMedia$: Observable = + combineLatest([this.spotlight$, this.pip$], (spotlight, pip) => ({ type: "spotlight-expanded", spotlight, pip: pip ?? undefined, })); - private readonly oneOnOneLayoutMedia: Observable = - this.mediaItems.pipe( + private readonly oneOnOneLayoutMedia$: Observable = + this.mediaItems$.pipe( map((mediaItems) => { if (mediaItems.length !== 2) return null; const local = mediaItems.find((vm) => vm.vm.local)?.vm as @@ -888,86 +887,91 @@ export class CallViewModel extends ViewModel { }), ); - private readonly pipLayoutMedia: Observable = - this.spotlight.pipe(map((spotlight) => ({ type: "pip", spotlight }))); + private readonly pipLayoutMedia$: Observable = + this.spotlight$.pipe(map((spotlight) => ({ type: "pip", spotlight }))); /** * The media to be used to produce a layout. */ - private readonly layoutMedia: Observable = this.windowMode.pipe( - switchMap((windowMode) => { - switch (windowMode) { - case "normal": - return this.gridMode.pipe( - switchMap((gridMode) => { - switch (gridMode) { - case "grid": - return this.oneOnOneLayoutMedia.pipe( - switchMap((oneOnOne) => - oneOnOne === null ? this.gridLayoutMedia : of(oneOnOne), - ), - ); - case "spotlight": - return this.spotlightExpanded.pipe( - switchMap((expanded) => - expanded - ? this.spotlightExpandedLayoutMedia - : this.spotlightLandscapeLayoutMedia, - ), - ); - } - }), - ); - case "narrow": - return this.oneOnOneLayoutMedia.pipe( - switchMap((oneOnOne) => - oneOnOne === null - ? combineLatest( - [this.grid, this.spotlight], - (grid, spotlight) => - grid.length > smallMobileCallThreshold || - spotlight.some((vm) => vm instanceof ScreenShareViewModel) - ? this.spotlightPortraitLayoutMedia - : this.gridLayoutMedia, - ).pipe(switchAll()) - : // The expanded spotlight layout makes for a better one-on-one - // experience in narrow windows - this.spotlightExpandedLayoutMedia, - ), - ); - case "flat": - return this.gridMode.pipe( - switchMap((gridMode) => { - switch (gridMode) { - case "grid": - // Yes, grid mode actually gets you a "spotlight" layout in - // this window mode. - return this.spotlightLandscapeLayoutMedia; - case "spotlight": - return this.spotlightExpandedLayoutMedia; - } - }), - ); - case "pip": - return this.pipLayoutMedia; - } - }), - this.scope.state(), - ); + private readonly layoutMedia$: Observable = + this.windowMode$.pipe( + switchMap((windowMode) => { + switch (windowMode) { + case "normal": + return this.gridMode$.pipe( + switchMap((gridMode) => { + switch (gridMode) { + case "grid": + return this.oneOnOneLayoutMedia$.pipe( + switchMap((oneOnOne) => + oneOnOne === null + ? this.gridLayoutMedia$ + : of(oneOnOne), + ), + ); + case "spotlight": + return this.spotlightExpanded$.pipe( + switchMap((expanded) => + expanded + ? this.spotlightExpandedLayoutMedia$ + : this.spotlightLandscapeLayoutMedia$, + ), + ); + } + }), + ); + case "narrow": + return this.oneOnOneLayoutMedia$.pipe( + switchMap((oneOnOne) => + oneOnOne === null + ? combineLatest( + [this.grid$, this.spotlight$], + (grid, spotlight) => + grid.length > smallMobileCallThreshold || + spotlight.some( + (vm) => vm instanceof ScreenShareViewModel, + ) + ? this.spotlightPortraitLayoutMedia$ + : this.gridLayoutMedia$, + ).pipe(switchAll()) + : // The expanded spotlight layout makes for a better one-on-one + // experience in narrow windows + this.spotlightExpandedLayoutMedia$, + ), + ); + case "flat": + return this.gridMode$.pipe( + switchMap((gridMode) => { + switch (gridMode) { + case "grid": + // Yes, grid mode actually gets you a "spotlight" layout in + // this window mode. + return this.spotlightLandscapeLayoutMedia$; + case "spotlight": + return this.spotlightExpandedLayoutMedia$; + } + }), + ); + case "pip": + return this.pipLayoutMedia$; + } + }), + this.scope.state(), + ); // 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 // are visible, and loop the data back into the layouts with a Subject. - private readonly visibleTiles = new Subject(); + private readonly visibleTiles$ = new Subject(); private readonly setVisibleTiles = (value: number): void => - this.visibleTiles.next(value); + this.visibleTiles$.next(value); - public readonly layoutInternals: Observable< + public readonly layoutInternals$: Observable< LayoutScanState & { layout: Layout } > = combineLatest([ - this.layoutMedia, - this.visibleTiles.pipe(startWith(0), distinctUntilChanged()), + this.layoutMedia$, + this.visibleTiles$.pipe(startWith(0), distinctUntilChanged()), ]).pipe( scan< [LayoutMedia, number], @@ -1009,7 +1013,7 @@ export class CallViewModel extends ViewModel { /** * The layout of tiles in the call interface. */ - public readonly layout: Observable = this.layoutInternals.pipe( + public readonly layout$: Observable = this.layoutInternals$.pipe( map(({ layout }) => layout), this.scope.state(), ); @@ -1017,18 +1021,18 @@ export class CallViewModel extends ViewModel { /** * The current generation of the tile store, exposed for debugging purposes. */ - public readonly tileStoreGeneration: Observable = - this.layoutInternals.pipe( + public readonly tileStoreGeneration$: Observable = + this.layoutInternals$.pipe( map(({ tiles }) => tiles.generation), this.scope.state(), ); - public showSpotlightIndicators: Observable = this.layout.pipe( + public showSpotlightIndicators$: Observable = this.layout$.pipe( map((l) => l.type !== "grid"), this.scope.state(), ); - public showSpeakingIndicators: Observable = this.layout.pipe( + public showSpeakingIndicators$: Observable = this.layout$.pipe( switchMap((l) => { switch (l.type) { case "spotlight-landscape": @@ -1036,7 +1040,7 @@ export class CallViewModel extends ViewModel { // If the spotlight is showing the active speaker, we can do without // speaking indicators as they're a redundant visual cue. But if // screen sharing feeds are in the spotlight we still need them. - return l.spotlight.media.pipe( + return l.spotlight.media$.pipe( map((models: MediaViewModel[]) => models.some((m) => m instanceof ScreenShareViewModel), ), @@ -1055,11 +1059,11 @@ export class CallViewModel extends ViewModel { this.scope.state(), ); - public readonly toggleSpotlightExpanded: Observable<(() => void) | null> = - this.windowMode.pipe( + public readonly toggleSpotlightExpanded$: Observable<(() => void) | null> = + this.windowMode$.pipe( switchMap((mode) => mode === "normal" - ? this.layout.pipe( + ? this.layout$.pipe( map( (l) => l.type === "spotlight-landscape" || @@ -1070,50 +1074,50 @@ export class CallViewModel extends ViewModel { ), distinctUntilChanged(), map((enabled) => - enabled ? (): void => this.spotlightExpandedToggle.next() : null, + enabled ? (): void => this.spotlightExpandedToggle$.next() : null, ), this.scope.state(), ); - private readonly screenTap = new Subject(); - private readonly controlsTap = new Subject(); - private readonly screenHover = new Subject(); - private readonly screenUnhover = new Subject(); + private readonly screenTap$ = new Subject(); + private readonly controlsTap$ = new Subject(); + private readonly screenHover$ = new Subject(); + private readonly screenUnhover$ = new Subject(); /** * Callback for when the user taps the call view. */ public tapScreen(): void { - this.screenTap.next(); + this.screenTap$.next(); } /** * Callback for when the user taps the call's controls. */ public tapControls(): void { - this.controlsTap.next(); + this.controlsTap$.next(); } /** * Callback for when the user hovers over the call view. */ public hoverScreen(): void { - this.screenHover.next(); + this.screenHover$.next(); } /** * Callback for when the user stops hovering over the call view. */ public unhoverScreen(): void { - this.screenUnhover.next(); + this.screenUnhover$.next(); } - public readonly showHeader: Observable = this.windowMode.pipe( + public readonly showHeader$: Observable = this.windowMode$.pipe( map((mode) => mode !== "pip" && mode !== "flat"), this.scope.state(), ); - public readonly showFooter: Observable = this.windowMode.pipe( + public readonly showFooter$: Observable = this.windowMode$.pipe( switchMap((mode) => { switch (mode) { case "pip": @@ -1128,9 +1132,9 @@ export class CallViewModel extends ViewModel { if (isFirefox()) return of(true); // Show/hide the footer in response to interactions return merge( - this.screenTap.pipe(map(() => "tap screen" as const)), - this.controlsTap.pipe(map(() => "tap controls" as const)), - this.screenHover.pipe(map(() => "hover" as const)), + this.screenTap$.pipe(map(() => "tap screen" as const)), + this.controlsTap$.pipe(map(() => "tap controls" as const)), + this.screenHover$.pipe(map(() => "hover" as const)), ).pipe( switchScan((state, interaction) => { switch (interaction) { @@ -1153,7 +1157,7 @@ export class CallViewModel extends ViewModel { // Show on hover and hide after a timeout return race( timer(showFooterMs), - this.screenUnhover.pipe(take(1)), + this.screenUnhover$.pipe(take(1)), ).pipe( map(() => false), startWith(true), @@ -1172,7 +1176,7 @@ export class CallViewModel extends ViewModel { private readonly matrixRTCSession: MatrixRTCSession, private readonly livekitRoom: LivekitRoom, private readonly encryptionSystem: EncryptionSystem, - private readonly connectionState: Observable, + private readonly connectionState$: Observable, ) { super(); } diff --git a/src/state/MediaViewModel.test.ts b/src/state/MediaViewModel.test.ts index c4e0bee6..18fa13b6 100644 --- a/src/state/MediaViewModel.test.ts +++ b/src/state/MediaViewModel.test.ts @@ -49,7 +49,7 @@ test("control a participant's volume", async () => { expect(setVolumeSpy).toHaveBeenLastCalledWith(0.8); }, }); - expectObservable(vm.localVolume).toBe("ab(cd)(ef)g", { + expectObservable(vm.localVolume$).toBe("ab(cd)(ef)g", { a: 1, b: 0, c: 0.6, @@ -69,7 +69,7 @@ test("toggle fit/contain for a participant's video", async () => { a: () => vm.toggleFitContain(), b: () => vm.toggleFitContain(), }); - expectObservable(vm.cropVideo).toBe("abc", { + expectObservable(vm.cropVideo$).toBe("abc", { a: true, b: false, c: true, @@ -82,7 +82,7 @@ test("local media remembers whether it should always be shown", async () => { await withLocalMedia(rtcMembership, {}, (vm) => withTestScheduler(({ expectObservable, schedule }) => { schedule("-a|", { a: () => vm.setAlwaysShow(false) }); - expectObservable(vm.alwaysShow).toBe("ab", { a: true, b: false }); + expectObservable(vm.alwaysShow$).toBe("ab", { a: true, b: false }); }), ); // Next local media should start out *not* always shown @@ -93,7 +93,7 @@ test("local media remembers whether it should always be shown", async () => { (vm) => withTestScheduler(({ expectObservable, schedule }) => { schedule("-a|", { a: () => vm.setAlwaysShow(true) }); - expectObservable(vm.alwaysShow).toBe("ab", { a: false, b: true }); + expectObservable(vm.alwaysShow$).toBe("ab", { a: false, b: true }); }), ); }); diff --git a/src/state/MediaViewModel.ts b/src/state/MediaViewModel.ts index ea015eb8..8100a50d 100644 --- a/src/state/MediaViewModel.ts +++ b/src/state/MediaViewModel.ts @@ -74,11 +74,11 @@ export function useDisplayName(vm: MediaViewModel): string { return displayName; } -export function observeTrackReference( - participant: Observable, +export function observeTrackReference$( + participant$: Observable, source: Track.Source, ): Observable { - return participant.pipe( + return participant$.pipe( switchMap((p) => { if (p) { return observeParticipantMedia(p).pipe( @@ -96,7 +96,7 @@ export function observeTrackReference( ); } -function observeRemoteTrackReceivingOkay( +function observeRemoteTrackReceivingOkay$( participant: Participant, source: Track.Source, ): Observable { @@ -111,7 +111,7 @@ function observeRemoteTrackReceivingOkay( }; return combineLatest([ - observeTrackReference(of(participant), source), + observeTrackReference$(of(participant), source), interval(1000).pipe(startWith(0)), ]).pipe( switchMap(async ([trackReference]) => { @@ -168,7 +168,7 @@ function observeRemoteTrackReceivingOkay( ); } -function encryptionErrorObservable( +function encryptionErrorObservable$( room: LivekitRoom, participant: Participant, encryptionSystem: EncryptionSystem, @@ -209,13 +209,13 @@ abstract class BaseMediaViewModel extends ViewModel { /** * The LiveKit video track for this media. */ - public readonly video: Observable; + public readonly video$: Observable; /** * Whether there should be a warning that this media is unencrypted. */ - public readonly unencryptedWarning: Observable; + public readonly unencryptedWarning$: Observable; - public readonly encryptionStatus: Observable; + public readonly encryptionStatus$: Observable; /** * Whether this media corresponds to the local participant. @@ -235,7 +235,7 @@ abstract class BaseMediaViewModel extends ViewModel { public readonly member: RoomMember | undefined, // We don't necessarily have a participant if a user connects via MatrixRTC but not (yet) through // livekit. - protected readonly participant: Observable< + protected readonly participant$: Observable< LocalParticipant | RemoteParticipant | undefined >, @@ -245,21 +245,21 @@ abstract class BaseMediaViewModel extends ViewModel { livekitRoom: LivekitRoom, ) { super(); - const audio = observeTrackReference(participant, audioSource).pipe( + const audio$ = observeTrackReference$(participant$, audioSource).pipe( this.scope.state(), ); - this.video = observeTrackReference(participant, videoSource).pipe( + this.video$ = observeTrackReference$(participant$, videoSource).pipe( this.scope.state(), ); - this.unencryptedWarning = combineLatest( - [audio, this.video], + this.unencryptedWarning$ = combineLatest( + [audio$, this.video$], (a, v) => encryptionSystem.kind !== E2eeType.NONE && (a?.publication?.isEncrypted === false || v?.publication?.isEncrypted === false), ).pipe(this.scope.state()); - this.encryptionStatus = this.participant.pipe( + this.encryptionStatus$ = this.participant$.pipe( switchMap((participant): Observable => { if (!participant) { return of(EncryptionStatus.Connecting); @@ -270,20 +270,20 @@ abstract class BaseMediaViewModel extends ViewModel { return of(EncryptionStatus.Okay); } else if (encryptionSystem.kind === E2eeType.PER_PARTICIPANT) { return combineLatest([ - encryptionErrorObservable( + encryptionErrorObservable$( livekitRoom, participant, encryptionSystem, "MissingKey", ), - encryptionErrorObservable( + encryptionErrorObservable$( livekitRoom, participant, encryptionSystem, "InvalidKey", ), - observeRemoteTrackReceivingOkay(participant, audioSource), - observeRemoteTrackReceivingOkay(participant, videoSource), + observeRemoteTrackReceivingOkay$(participant, audioSource), + observeRemoteTrackReceivingOkay$(participant, videoSource), ]).pipe( map(([keyMissing, keyInvalid, audioOkay, videoOkay]) => { if (keyMissing) return EncryptionStatus.KeyMissing; @@ -296,14 +296,14 @@ abstract class BaseMediaViewModel extends ViewModel { ); } else { return combineLatest([ - encryptionErrorObservable( + encryptionErrorObservable$( livekitRoom, participant, encryptionSystem, "InvalidKey", ), - observeRemoteTrackReceivingOkay(participant, audioSource), - observeRemoteTrackReceivingOkay(participant, videoSource), + observeRemoteTrackReceivingOkay$(participant, audioSource), + observeRemoteTrackReceivingOkay$(participant, videoSource), ]).pipe( map( ([keyInvalid, audioOkay, videoOkay]): @@ -339,7 +339,7 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel { /** * Whether the participant is speaking. */ - public readonly speaking = this.participant.pipe( + public readonly speaking$ = this.participant$.pipe( switchMap((p) => p ? observeParticipantEvents(p, ParticipantEvent.IsSpeakingChanged).pipe( @@ -353,49 +353,49 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel { /** * Whether this participant is sending audio (i.e. is unmuted on their side). */ - public readonly audioEnabled: Observable; + public readonly audioEnabled$: Observable; /** * Whether this participant is sending video. */ - public readonly videoEnabled: Observable; + public readonly videoEnabled$: Observable; - private readonly _cropVideo = new BehaviorSubject(true); + private readonly _cropVideo$ = new BehaviorSubject(true); /** * Whether the tile video should be contained inside the tile or be cropped to fit. */ - public readonly cropVideo: Observable = this._cropVideo; + public readonly cropVideo$: Observable = this._cropVideo$; public constructor( id: string, member: RoomMember | undefined, - participant: Observable, + participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, ) { super( id, member, - participant, + participant$, encryptionSystem, Track.Source.Microphone, Track.Source.Camera, livekitRoom, ); - const media = participant.pipe( + const media$ = participant$.pipe( switchMap((p) => (p && observeParticipantMedia(p)) ?? of(undefined)), this.scope.state(), ); - this.audioEnabled = media.pipe( + this.audioEnabled$ = media$.pipe( map((m) => m?.microphoneTrack?.isMuted === false), ); - this.videoEnabled = media.pipe( + this.videoEnabled$ = media$.pipe( map((m) => m?.cameraTrack?.isMuted === false), ); } public toggleFitContain(): void { - this._cropVideo.next(!this._cropVideo.value); + this._cropVideo$.next(!this._cropVideo$.value); } public get local(): boolean { @@ -410,7 +410,7 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel { /** * Whether the video should be mirrored. */ - public readonly mirror = this.video.pipe( + public readonly mirror$ = this.video$.pipe( switchMap((v) => { const track = v?.publication?.track; if (!(track instanceof LocalTrack)) return of(false); @@ -428,17 +428,17 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel { * Whether to show this tile in a highly visible location near the start of * the grid. */ - public readonly alwaysShow = alwaysShowSelf.value; + public readonly alwaysShow$ = alwaysShowSelf.value$; public readonly setAlwaysShow = alwaysShowSelf.setValue; public constructor( id: string, member: RoomMember | undefined, - participant: Observable, + participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, ) { - super(id, member, participant, encryptionSystem, livekitRoom); + super(id, member, participant$, encryptionSystem, livekitRoom); } } @@ -446,18 +446,18 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel { * A remote participant's user media. */ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel { - private readonly locallyMutedToggle = new Subject(); - private readonly localVolumeAdjustment = new Subject(); - private readonly localVolumeCommit = new Subject(); + private readonly locallyMutedToggle$ = new Subject(); + private readonly localVolumeAdjustment$ = new Subject(); + private readonly localVolumeCommit$ = new Subject(); /** * The volume to which this participant's audio is set, as a scalar * multiplier. */ - public readonly localVolume: Observable = merge( - this.locallyMutedToggle.pipe(map(() => "toggle mute" as const)), - this.localVolumeAdjustment, - this.localVolumeCommit.pipe(map(() => "commit" as const)), + public readonly localVolume$: Observable = merge( + this.locallyMutedToggle$.pipe(map(() => "toggle mute" as const)), + this.localVolumeAdjustment$, + this.localVolumeCommit$.pipe(map(() => "commit" as const)), ).pipe( accumulate({ volume: 1, committedVolume: 1 }, (state, event) => { switch (event) { @@ -487,7 +487,7 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel { /** * Whether this participant's audio is disabled. */ - public readonly locallyMuted: Observable = this.localVolume.pipe( + public readonly locallyMuted$: Observable = this.localVolume$.pipe( map((volume) => volume === 0), this.scope.state(), ); @@ -495,29 +495,29 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel { public constructor( id: string, member: RoomMember | undefined, - participant: Observable, + participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, ) { - super(id, member, participant, encryptionSystem, livekitRoom); + super(id, member, participant$, encryptionSystem, livekitRoom); // Sync the local volume with LiveKit combineLatest([ - participant, - this.localVolume.pipe(this.scope.bind()), + participant$, + this.localVolume$.pipe(this.scope.bind()), ]).subscribe(([p, volume]) => p && p.setVolume(volume)); } public toggleLocallyMuted(): void { - this.locallyMutedToggle.next(); + this.locallyMutedToggle$.next(); } public setLocalVolume(value: number): void { - this.localVolumeAdjustment.next(value); + this.localVolumeAdjustment$.next(value); } public commitLocalVolume(): void { - this.localVolumeCommit.next(); + this.localVolumeCommit$.next(); } } @@ -528,7 +528,7 @@ export class ScreenShareViewModel extends BaseMediaViewModel { public constructor( id: string, member: RoomMember | undefined, - participant: Observable, + participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, public readonly local: boolean, @@ -536,7 +536,7 @@ export class ScreenShareViewModel extends BaseMediaViewModel { super( id, member, - participant, + participant$, encryptionSystem, Track.Source.ScreenShareAudio, Track.Source.ScreenShare, diff --git a/src/state/ObservableScope.ts b/src/state/ObservableScope.ts index 5a2e0e9a..254fc03f 100644 --- a/src/state/ObservableScope.ts +++ b/src/state/ObservableScope.ts @@ -19,9 +19,9 @@ type MonoTypeOperator = (o: Observable) => Observable; * A scope which limits the execution lifetime of its bound Observables. */ export class ObservableScope { - private readonly ended = new Subject(); + private readonly ended$ = new Subject(); - private readonly bindImpl: MonoTypeOperator = takeUntil(this.ended); + private readonly bindImpl: MonoTypeOperator = takeUntil(this.ended$); /** * Binds an Observable to this scope, so that it completes when the scope @@ -31,8 +31,8 @@ export class ObservableScope { return this.bindImpl; } - private readonly stateImpl: MonoTypeOperator = (o) => - o.pipe( + private readonly stateImpl: MonoTypeOperator = (o$) => + o$.pipe( this.bind(), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: false }), @@ -51,7 +51,7 @@ export class ObservableScope { * Ends the scope, causing any bound Observables to complete. */ public end(): void { - this.ended.next(); - this.ended.complete(); + this.ended$.next(); + this.ended$.complete(); } } diff --git a/src/state/TileStore.ts b/src/state/TileStore.ts index cd269944..4d6878b6 100644 --- a/src/state/TileStore.ts +++ b/src/state/TileStore.ts @@ -18,31 +18,31 @@ function debugEntries(entries: GridTileData[]): string[] { } let DEBUG_ENABLED = false; -debugTileLayout.value.subscribe((value) => (DEBUG_ENABLED = value)); +debugTileLayout.value$.subscribe((value) => (DEBUG_ENABLED = value)); class SpotlightTileData { - private readonly media_: BehaviorSubject; + private readonly media$: BehaviorSubject; public get media(): MediaViewModel[] { - return this.media_.value; + return this.media$.value; } public set media(value: MediaViewModel[]) { - this.media_.next(value); + this.media$.next(value); } - private readonly maximised_: BehaviorSubject; + private readonly maximised$: BehaviorSubject; public get maximised(): boolean { - return this.maximised_.value; + return this.maximised$.value; } public set maximised(value: boolean) { - this.maximised_.next(value); + this.maximised$.next(value); } public readonly vm: SpotlightTileViewModel; public constructor(media: MediaViewModel[], maximised: boolean) { - this.media_ = new BehaviorSubject(media); - this.maximised_ = new BehaviorSubject(maximised); - this.vm = new SpotlightTileViewModel(this.media_, this.maximised_); + this.media$ = new BehaviorSubject(media); + this.maximised$ = new BehaviorSubject(maximised); + this.vm = new SpotlightTileViewModel(this.media$, this.maximised$); } public destroy(): void { @@ -51,19 +51,19 @@ class SpotlightTileData { } class GridTileData { - private readonly media_: BehaviorSubject; + private readonly media$: BehaviorSubject; public get media(): UserMediaViewModel { - return this.media_.value; + return this.media$.value; } public set media(value: UserMediaViewModel) { - this.media_.next(value); + this.media$.next(value); } public readonly vm: GridTileViewModel; public constructor(media: UserMediaViewModel) { - this.media_ = new BehaviorSubject(media); - this.vm = new GridTileViewModel(this.media_); + this.media$ = new BehaviorSubject(media); + this.vm = new GridTileViewModel(this.media$); } public destroy(): void { @@ -123,7 +123,10 @@ export class TileStoreBuilder { "speaking" in this.prevSpotlight.media[0] && this.prevSpotlight.media[0]; - private readonly prevGridByMedia = new Map( + private readonly prevGridByMedia: Map< + MediaViewModel, + [GridTileData, number] + > = new Map( this.prevGrid.map((entry, i) => [entry.media, [entry, i]] as const), ); diff --git a/src/state/TileViewModel.ts b/src/state/TileViewModel.ts index 612d7033..5815df54 100644 --- a/src/state/TileViewModel.ts +++ b/src/state/TileViewModel.ts @@ -18,15 +18,15 @@ function createId(): string { export class GridTileViewModel extends ViewModel { public readonly id = createId(); - public constructor(public readonly media: Observable) { + public constructor(public readonly media$: Observable) { super(); } } export class SpotlightTileViewModel extends ViewModel { public constructor( - public readonly media: Observable, - public readonly maximised: Observable, + public readonly media$: Observable, + public readonly maximised$: Observable, ) { super(); } diff --git a/src/state/observeSpeaker.test.ts b/src/state/observeSpeaker.test.ts index daa5f033..2a73482c 100644 --- a/src/state/observeSpeaker.test.ts +++ b/src/state/observeSpeaker.test.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { describe, test } from "vitest"; import { withTestScheduler } from "../utils/test"; -import { observeSpeaker } from "./observeSpeaker"; +import { observeSpeaker$ } from "./observeSpeaker"; const yesNo = { y: true, @@ -22,40 +22,36 @@ describe("observeSpeaker", () => { // should default to false when no input is given const speakingInputMarbles = ""; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); test("after no speaking", () => { const speakingInputMarbles = "n"; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); test("with speaking for 1ms", () => { const speakingInputMarbles = "y n"; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); test("with speaking for 999ms", () => { const speakingInputMarbles = "y 999ms n"; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); @@ -63,20 +59,18 @@ describe("observeSpeaker", () => { const speakingInputMarbles = "y 199ms n 199ms y 199ms n 199ms y 199ms n 199ms y 199ms n 199ms y 199ms n 199ms y 199ms n 199ms y 199ms n 199ms y 199ms n"; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); test("with consecutive speaking then stops speaking", () => { const speakingInputMarbles = "y y y y y y y y y y n"; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); }); @@ -87,10 +81,9 @@ describe("observeSpeaker", () => { const speakingInputMarbles = " y"; const expectedOutputMarbles = "n 999ms y"; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); @@ -98,10 +91,9 @@ describe("observeSpeaker", () => { const speakingInputMarbles = " y 1s n "; const expectedOutputMarbles = "n 999ms y 60s n"; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); @@ -109,10 +101,9 @@ describe("observeSpeaker", () => { const speakingInputMarbles = " y 5s n "; const expectedOutputMarbles = "n 999ms y 64s n"; withTestScheduler(({ hot, expectObservable }) => { - expectObservable(observeSpeaker(hot(speakingInputMarbles, yesNo))).toBe( - expectedOutputMarbles, - yesNo, - ); + expectObservable( + observeSpeaker$(hot(speakingInputMarbles, yesNo)), + ).toBe(expectedOutputMarbles, yesNo); }); }); }); diff --git a/src/state/observeSpeaker.ts b/src/state/observeSpeaker.ts index cce43ef9..8817af25 100644 --- a/src/state/observeSpeaker.ts +++ b/src/state/observeSpeaker.ts @@ -18,16 +18,16 @@ import { * Require 1 second of continuous speaking to become a speaker, and 60 second of * continuous silence to stop being considered a speaker */ -export function observeSpeaker( - isSpeakingObservable: Observable, +export function observeSpeaker$( + isSpeakingObservable$: Observable, ): Observable { - const distinct = isSpeakingObservable.pipe(distinctUntilChanged()); + const distinct$ = isSpeakingObservable$.pipe(distinctUntilChanged()); - return distinct.pipe( + return distinct$.pipe( // Either change to the new value after the timer or re-emit the same value if it toggles back // (audit will return the latest (toggled back) value) before the timeout. audit((s) => - merge(timer(s ? 1000 : 60000), distinct.pipe(filter((s1) => s1 !== s))), + merge(timer(s ? 1000 : 60000), distinct$.pipe(filter((s1) => s1 !== s))), ), // Filter the re-emissions (marked as: | ) that happen if we toggle quickly (<1s) from false->true->false|->.. startWith(false), diff --git a/src/tile/GridTile.tsx b/src/tile/GridTile.tsx index 73c17527..8c6b2d9b 100644 --- a/src/tile/GridTile.tsx +++ b/src/tile/GridTile.tsx @@ -83,13 +83,13 @@ const UserMediaTile = forwardRef( ref, ) => { const { t } = useTranslation(); - const video = useObservableEagerState(vm.video); - const unencryptedWarning = useObservableEagerState(vm.unencryptedWarning); - const encryptionStatus = useObservableEagerState(vm.encryptionStatus); - const audioEnabled = useObservableEagerState(vm.audioEnabled); - const videoEnabled = useObservableEagerState(vm.videoEnabled); - const speaking = useObservableEagerState(vm.speaking); - const cropVideo = useObservableEagerState(vm.cropVideo); + const video = useObservableEagerState(vm.video$); + const unencryptedWarning = useObservableEagerState(vm.unencryptedWarning$); + const encryptionStatus = useObservableEagerState(vm.encryptionStatus$); + const audioEnabled = useObservableEagerState(vm.audioEnabled$); + const videoEnabled = useObservableEagerState(vm.videoEnabled$); + const speaking = useObservableEagerState(vm.speaking$); + const cropVideo = useObservableEagerState(vm.cropVideo$); const onSelectFitContain = useCallback( (e: Event) => { e.preventDefault(); @@ -198,8 +198,8 @@ interface LocalUserMediaTileProps extends TileProps { const LocalUserMediaTile = forwardRef( ({ vm, onOpenProfile, ...props }, ref) => { const { t } = useTranslation(); - const mirror = useObservableEagerState(vm.mirror); - const alwaysShow = useObservableEagerState(vm.alwaysShow); + const mirror = useObservableEagerState(vm.mirror$); + const alwaysShow = useObservableEagerState(vm.alwaysShow$); const latestAlwaysShow = useLatest(alwaysShow); const onSelectAlwaysShow = useCallback( (e: Event) => { @@ -249,8 +249,8 @@ const RemoteUserMediaTile = forwardRef< RemoteUserMediaTileProps >(({ vm, ...props }, ref) => { const { t } = useTranslation(); - const locallyMuted = useObservableEagerState(vm.locallyMuted); - const localVolume = useObservableEagerState(vm.localVolume); + const locallyMuted = useObservableEagerState(vm.locallyMuted$); + const localVolume = useObservableEagerState(vm.localVolume$); const onSelectMute = useCallback( (e: Event) => { e.preventDefault(); @@ -316,7 +316,7 @@ export const GridTile = forwardRef( ({ vm, onOpenProfile, ...props }, theirRef) => { const ourRef = useRef(null); const ref = useMergedRefs(ourRef, theirRef); - const media = useObservableEagerState(vm.media); + const media = useObservableEagerState(vm.media$); const displayName = useDisplayName(media); if (media instanceof LocalUserMediaViewModel) { diff --git a/src/tile/SpotlightTile.tsx b/src/tile/SpotlightTile.tsx index a1c3d46f..c72bad81 100644 --- a/src/tile/SpotlightTile.tsx +++ b/src/tile/SpotlightTile.tsx @@ -72,7 +72,7 @@ const SpotlightLocalUserMediaItem = forwardRef< HTMLDivElement, SpotlightLocalUserMediaItemProps >(({ vm, ...props }, ref) => { - const mirror = useObservableEagerState(vm.mirror); + const mirror = useObservableEagerState(vm.mirror$); return ; }); @@ -86,8 +86,8 @@ const SpotlightUserMediaItem = forwardRef< HTMLDivElement, SpotlightUserMediaItemProps >(({ vm, ...props }, ref) => { - const videoEnabled = useObservableEagerState(vm.videoEnabled); - const cropVideo = useObservableEagerState(vm.cropVideo); + const videoEnabled = useObservableEagerState(vm.videoEnabled$); + const cropVideo = useObservableEagerState(vm.cropVideo$); const baseProps: SpotlightUserMediaItemBaseProps & RefAttributes = { @@ -110,7 +110,7 @@ interface SpotlightItemProps { vm: MediaViewModel; targetWidth: number; targetHeight: number; - intersectionObserver: Observable; + intersectionObserver$: Observable; /** * Whether this item should act as a scroll snapping point. */ @@ -124,7 +124,7 @@ const SpotlightItem = forwardRef( vm, targetWidth, targetHeight, - intersectionObserver, + intersectionObserver$, snap, "aria-hidden": ariaHidden, }, @@ -133,15 +133,15 @@ const SpotlightItem = forwardRef( const ourRef = useRef(null); const ref = useMergedRefs(ourRef, theirRef); const displayName = useDisplayName(vm); - const video = useObservableEagerState(vm.video); - const unencryptedWarning = useObservableEagerState(vm.unencryptedWarning); - const encryptionStatus = useObservableEagerState(vm.encryptionStatus); + const video = useObservableEagerState(vm.video$); + const unencryptedWarning = useObservableEagerState(vm.unencryptedWarning$); + const encryptionStatus = useObservableEagerState(vm.encryptionStatus$); // Hook this item up to the intersection observer useEffect(() => { const element = ourRef.current!; let prevIo: IntersectionObserver | null = null; - const subscription = intersectionObserver.subscribe((io) => { + const subscription = intersectionObserver$.subscribe((io) => { prevIo?.unobserve(element); io.observe(element); prevIo = io; @@ -150,7 +150,7 @@ const SpotlightItem = forwardRef( subscription.unsubscribe(); prevIo?.unobserve(element); }; - }, [intersectionObserver]); + }, [intersectionObserver$]); const baseProps: SpotlightItemBaseProps & RefAttributes = { ref, @@ -208,10 +208,10 @@ export const SpotlightTile = forwardRef( theirRef, ) => { const { t } = useTranslation(); - const [ourRef, root] = useObservableRef(null); + const [ourRef, root$] = useObservableRef(null); const ref = useMergedRefs(ourRef, theirRef); - const maximised = useObservableEagerState(vm.maximised); - const media = useObservableEagerState(vm.media); + const maximised = useObservableEagerState(vm.maximised$); + const media = useObservableEagerState(vm.media$); const [visibleId, setVisibleId] = useState( media[0]?.id, ); @@ -225,9 +225,9 @@ export const SpotlightTile = forwardRef( // hooked up to the root element and the items. Because the items will run // their effects before their parent does, we need to do this dance with an // Observable to actually give them the intersection observer. - const intersectionObserver = useInitial>( + const intersectionObserver$ = useInitial>( () => - root.pipe( + root$.pipe( map( (r) => new IntersectionObserver( @@ -295,7 +295,7 @@ export const SpotlightTile = forwardRef( vm={vm} targetWidth={targetWidth} targetHeight={targetHeight} - intersectionObserver={intersectionObserver} + intersectionObserver$={intersectionObserver$} // This is how we get the container to scroll to the right media // when the previous/next buttons are clicked: we temporarily // remove all scroll snap points except for just the one media diff --git a/src/utils/observable.ts b/src/utils/observable.ts index a54c0293..977bdf79 100644 --- a/src/utils/observable.ts +++ b/src/utils/observable.ts @@ -15,10 +15,10 @@ const nothing = Symbol("nothing"); * callback will not be invoked. */ export function finalizeValue(callback: (finalValue: T) => void) { - return (source: Observable): Observable => + return (source$: Observable): Observable => defer(() => { let finalValue: T | typeof nothing = nothing; - return source.pipe( + return source$.pipe( tap((value) => (finalValue = value)), finalize(() => { if (finalValue !== nothing) callback(finalValue); @@ -35,6 +35,6 @@ export function accumulate( initial: State, update: (state: State, event: Event) => State, ) { - return (events: Observable): Observable => - events.pipe(scan(update, initial), startWith(initial)); + return (events$: Observable): Observable => + events$.pipe(scan(update, initial), startWith(initial)); } diff --git a/src/utils/test.ts b/src/utils/test.ts index 1cd21f01..db0d8959 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -77,14 +77,14 @@ export function withTestScheduler( continuation({ ...helpers, schedule(marbles, actions) { - const actionsObservable = helpers + const actionsObservable$ = helpers .cold(marbles) .pipe(map((value) => actions[value]())); const results = Object.fromEntries( Object.keys(actions).map((value) => [value, undefined] as const), ); // Run the actions and verify that none of them error - helpers.expectObservable(actionsObservable).toBe(marbles, results); + helpers.expectObservable(actionsObservable$).toBe(marbles, results); }, }), ); @@ -157,16 +157,16 @@ export function mockMatrixRoom(room: Partial): MatrixRoom { export function mockLivekitRoom( room: Partial, { - remoteParticipants, - }: { remoteParticipants?: Observable } = {}, + remoteParticipants$, + }: { remoteParticipants$?: Observable } = {}, ): LivekitRoom { const livekitRoom = { ...mockEmitter(), ...room, } as Partial as LivekitRoom; - if (remoteParticipants) { + if (remoteParticipants$) { livekitRoom.remoteParticipants = new Map(); - remoteParticipants.subscribe((newRemoteParticipants) => { + remoteParticipants$.subscribe((newRemoteParticipants) => { livekitRoom.remoteParticipants.clear(); newRemoteParticipants.forEach((p) => { livekitRoom.remoteParticipants.set(p.identity, p); @@ -238,7 +238,7 @@ export async function withRemoteMedia( { kind: E2eeType.PER_PARTICIPANT, }, - mockLivekitRoom({}, { remoteParticipants: of([remoteParticipant]) }), + mockLivekitRoom({}, { remoteParticipants$: of([remoteParticipant]) }), ); try { await continuation(vm); @@ -277,9 +277,9 @@ export class MockRTCSession extends TypedEventEmitter< } public withMemberships( - rtcMembers: Observable[]>, + rtcMembers$: Observable[]>, ): MockRTCSession { - rtcMembers.subscribe((m) => { + rtcMembers$.subscribe((m) => { const old = this.memberships; // always prepend the local participant const updated = [this.localMembership, ...(m as CallMembership[])]; From 2b6acb9cce8e76dd4507fe499171bc17c4ffcedd Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 17 Dec 2024 10:17:37 +0000 Subject: [PATCH 59/72] Use afterEach from vitest for consistency (#2908) This slipped through the PR review. Whilst not harmful, for consistency we use it from vitest. --- src/Modal.test.tsx | 3 +-- src/room/CallEventAudioRenderer.test.tsx | 2 +- src/room/ReactionAudioRenderer.test.tsx | 2 +- src/room/ReactionsOverlay.test.tsx | 3 +-- src/useAudioContext.test.tsx | 3 +-- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Modal.test.tsx b/src/Modal.test.tsx index bb6fb0f7..6368c7d9 100644 --- a/src/Modal.test.tsx +++ b/src/Modal.test.tsx @@ -5,10 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { expect, test } from "vitest"; +import { expect, test, afterEach } from "vitest"; import { render } from "@testing-library/react"; import { type ReactNode, useState } from "react"; -import { afterEach } from "node:test"; import userEvent from "@testing-library/user-event"; import { Modal } from "./Modal"; diff --git a/src/room/CallEventAudioRenderer.test.tsx b/src/room/CallEventAudioRenderer.test.tsx index 6868de49..cc7f4eea 100644 --- a/src/room/CallEventAudioRenderer.test.tsx +++ b/src/room/CallEventAudioRenderer.test.tsx @@ -13,11 +13,11 @@ import { type MockedFunction, test, vitest, + afterEach, } from "vitest"; import { type MatrixClient } from "matrix-js-sdk/src/client"; import { ConnectionState } from "livekit-client"; import { BehaviorSubject, of } from "rxjs"; -import { afterEach } from "node:test"; import { act, type ReactNode } from "react"; import { type CallMembership, diff --git a/src/room/ReactionAudioRenderer.test.tsx b/src/room/ReactionAudioRenderer.test.tsx index 0ab283a9..afa2c6ff 100644 --- a/src/room/ReactionAudioRenderer.test.tsx +++ b/src/room/ReactionAudioRenderer.test.tsx @@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details. import { render } from "@testing-library/react"; import { afterAll, + afterEach, beforeEach, expect, test, @@ -17,7 +18,6 @@ import { } from "vitest"; import { TooltipProvider } from "@vector-im/compound-web"; import { act, type ReactNode } from "react"; -import { afterEach } from "node:test"; import { MockRoom, diff --git a/src/room/ReactionsOverlay.test.tsx b/src/room/ReactionsOverlay.test.tsx index 8ea17178..5c3f8bf9 100644 --- a/src/room/ReactionsOverlay.test.tsx +++ b/src/room/ReactionsOverlay.test.tsx @@ -6,10 +6,9 @@ Please see LICENSE in the repository root for full details. */ import { render } from "@testing-library/react"; -import { expect, test } from "vitest"; +import { expect, test, afterEach } from "vitest"; import { TooltipProvider } from "@vector-im/compound-web"; import { act, type ReactNode } from "react"; -import { afterEach } from "node:test"; import { MockRoom, diff --git a/src/useAudioContext.test.tsx b/src/useAudioContext.test.tsx index 2fda4add..9f97f1b6 100644 --- a/src/useAudioContext.test.tsx +++ b/src/useAudioContext.test.tsx @@ -5,10 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { expect, test, vitest } from "vitest"; +import { expect, test, vitest, afterEach } from "vitest"; import { type FC } from "react"; import { render } from "@testing-library/react"; -import { afterEach } from "node:test"; import userEvent from "@testing-library/user-event"; import { deviceStub, MediaDevicesContext } from "./livekit/MediaDevicesContext"; From 53fff37d5d36051a8372d006358fcf523170263c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 17 Dec 2024 16:44:50 +0000 Subject: [PATCH 60/72] Use AutoDiscovery.getRawClientConfig() instead of MatrixClient.getClientWellKnown() (#2906) I'm open to suggestions on what is sensible to test around this... --- src/rtcSessionHelper.test.ts | 13 ++++++++++++- src/rtcSessionHelpers.ts | 36 +++++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/rtcSessionHelper.test.ts b/src/rtcSessionHelper.test.ts index 7df9f1b3..797312f6 100644 --- a/src/rtcSessionHelper.test.ts +++ b/src/rtcSessionHelper.test.ts @@ -7,6 +7,7 @@ Please see LICENSE in the repository root for full details. import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { expect, test, vi } from "vitest"; +import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; import { enterRTCSession } from "../src/rtcSessionHelpers"; import { mockConfig } from "./utils/test"; @@ -36,11 +37,21 @@ test("It joins the correct Session", async () => { mockConfig({ livekit: { livekit_service_url: "http://my-default-service-url.com" }, }); + + vi.spyOn(AutoDiscovery, "getRawClientConfig").mockImplementation( + async (domain) => { + if (domain === "example.org") { + return Promise.resolve(clientWellKnown); + } + return Promise.resolve({}); + }, + ); + const mockedSession = vi.mocked({ room: { roomId: "roomId", client: { - getClientWellKnown: vi.fn().mockReturnValue(clientWellKnown), + getDomain: vi.fn().mockReturnValue("example.org"), }, }, memberships: [], diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index f1c7eb8c..3aa2fbbd 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -13,6 +13,7 @@ import { isLivekitFocus, isLivekitFocusConfig, } from "matrix-js-sdk/src/matrixrtc/LivekitFocus"; +import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; import { PosthogAnalytics } from "./analytics/PosthogAnalytics"; import { Config } from "./config/Config"; @@ -43,19 +44,28 @@ async function makePreferredLivekitFoci( preferredFoci.push(focusInUse); } - // Prioritize the client well known over the configured sfu. - const wellKnownFoci = - rtcSession.room.client.getClientWellKnown()?.[FOCI_WK_KEY]; - if (Array.isArray(wellKnownFoci)) { - preferredFoci.push( - ...wellKnownFoci - .filter((f) => !!f) - .filter(isLivekitFocusConfig) - .map((wellKnownFocus) => { - logger.log("Adding livekit focus from well known: ", wellKnownFocus); - return { ...wellKnownFocus, livekit_alias: livekitAlias }; - }), - ); + // Prioritize the .well-known/matrix/client, if available, over the configured SFU + const domain = rtcSession.room.client.getDomain(); + if (domain) { + // we use AutoDiscovery instead of relying on the MatrixClient having already + // been fully configured and started + const wellKnownFoci = (await AutoDiscovery.getRawClientConfig(domain))?.[ + FOCI_WK_KEY + ]; + if (Array.isArray(wellKnownFoci)) { + preferredFoci.push( + ...wellKnownFoci + .filter((f) => !!f) + .filter(isLivekitFocusConfig) + .map((wellKnownFocus) => { + logger.log( + "Adding livekit focus from well known: ", + wellKnownFocus, + ); + return { ...wellKnownFocus, livekit_alias: livekitAlias }; + }), + ); + } } const urlFromConf = Config.get().livekit?.livekit_service_url; From 6b8dddfaaad68fd0bd1f4f5c0c2fb7b351775ffe Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 18 Dec 2024 09:35:42 +0000 Subject: [PATCH 61/72] Bump matrix-js-sdk for bundle improvements (#2911) Brings in updated version of matrix-sdk-crypto-wasm which helps to reduce bundle size and lazy loading. Full diff https://github.com/matrix-org/matrix-js-sdk/compare/d1de32ea2773df4c6f8a956678bbd19b6d022475...e4182eb75227c283a18704727021e99ced72868d --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5fdd147f..d3b8da40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1961,10 +1961,10 @@ dependencies: "@bufbuild/protobuf" "^1.10.0" -"@matrix-org/matrix-sdk-crypto-wasm@^11.0.0": - version "11.0.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-11.0.0.tgz#c49a1a0d1e367d3c00a2144a4ab23caee0b1eec2" - integrity sha512-a7NUH8Kjc8hwzNCPpkOGXoceFqWJiWvA8OskXeDrKyODJuDz4yKrZ/nvgaVRfQe45Ab5UC1ZXYqaME+ChlJuqg== +"@matrix-org/matrix-sdk-crypto-wasm@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-12.0.0.tgz#e3a5150ccbb21d5e98ee3882e7057b9f17fb962a" + integrity sha512-nkkXAxUIk9UTso4TbU6Bgqsv/rJShXQXRx0ti/W+AWXHJ2HoH4sL5LsXkc7a8yYGn8tyXqxGPsYA1UeHqLwm0Q== "@matrix-org/olm@3.2.15": version "3.2.15" @@ -6568,10 +6568,10 @@ matrix-events-sdk@0.0.1: matrix-js-sdk@matrix-org/matrix-js-sdk#develop: version "34.13.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d1de32ea2773df4c6f8a956678bbd19b6d022475" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e4182eb75227c283a18704727021e99ced72868d" dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm" "^11.0.0" + "@matrix-org/matrix-sdk-crypto-wasm" "^12.0.0" "@matrix-org/olm" "3.2.15" another-json "^0.2.0" bs58 "^6.0.0" From 19d0f84f028da5afe15aa0285d13b28e54acb8d7 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 18 Dec 2024 15:30:33 +0000 Subject: [PATCH 62/72] Introduce intent URL param and make it change the default lobby behaviour (#2828) * Introduce `intent` URL param and make it change the default lobby behaviour * Mark skipLobby as deprecated * Add support for unknown intent which is default for when not specified --- docs/url-params.md | 57 ++++++++++++++++++++++--------------------- src/UrlParams.test.ts | 50 ++++++++++++++++++++++++++++++++++++- src/UrlParams.ts | 23 ++++++++++++++++- 3 files changed, 100 insertions(+), 30 deletions(-) diff --git a/docs/url-params.md b/docs/url-params.md index 010a4ec8..740e0218 100644 --- a/docs/url-params.md +++ b/docs/url-params.md @@ -32,31 +32,32 @@ There are two formats for Element Call urls. ## Parameters -| Name | Values | Required for widget | Required for SPA | Description | -| ------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `allowIceFallback` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Allows use of fallback STUN servers for ICE if the user's homeserver doesn’t provide any. | -| `analyticsID` | Posthog analytics ID | No | No | Available only with user's consent for sharing telemetry in Element Web. | -| `appPrompt` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Prompts the user to launch the native mobile app upon entering a room, applicable only on Android and iOS, and must be enabled in config. | -| `baseUrl` | | Yes | Not applicable | The base URL of the homeserver to use for media lookups. | -| `confineToRoom` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Keeps the user confined to the current call/room. | -| `deviceId` | Matrix device ID | Yes | Not applicable | The Matrix device ID for the widget host. | -| `displayName` | | No | No | Display name used for auto-registration. | -| `enableE2EE` (deprecated) | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Legacy flag to enable end-to-end encryption, not used in the `livekit` branch. | -| `fontScale` | A decimal number such as `0.9` | No, defaults to `1.0` | No, defaults to `1.0` | Factor by which to scale the interface's font size. | -| `fonts` | | No | No | Defines the font(s) used by the interface. Multiple font parameters can be specified: `?font=font-one&font=font-two...`. | -| `hideHeader` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the room header when in a call. | -| `hideScreensharing` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the screen-sharing button. | -| `homeserver` | | Not applicable | No | Homeserver for registering a new (guest) user, configures non-default guest user server when creating a spa link. | -| `lang` | [BCP 47](https://www.rfc-editor.org/info/bcp47) code | No | No | The language the app should use. | -| `parentUrl` | | Yes | Not applicable | The url used to send widget action postMessages. This should be the domain of the client or the webview the widget is hosted in. (in case the widget is not in an Iframe but in a dedicated webview we send the postMessages same WebView the widget lives in. Filtering is done in the widget so it ignores the messages it receives from itself) | -| `password` | | No | No | E2EE password when using a shared secret. (For individual sender keys in embedded mode this is not required.) | -| `perParticipantE2EE` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables per participant encryption with Keys exchanged over encrypted matrix room messages. | -| `preload` | `true` or `false` | No, defaults to `false` | Not applicable | Pauses app before joining a call until an `io.element.join` widget action is seen, allowing preloading. | -| `returnToLobby` | `true` or `false` | No, defaults to `false` | Not applicable | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. | -| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. | -| `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` | `true` or `false` | No, 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. | -| `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | Not applicable | The Matrix user ID. | -| `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. | -| `widgetId` | [MSC2774](https://github.com/matrix-org/matrix-spec-proposals/pull/2774) format widget ID | Yes | Not applicable | The id used by the widget. The presence of this parameter implies that element call will not connect to a homeserver directly and instead tries to establish postMessage communication via the `parentUrl`. | +| Name | Values | Required for widget | Required for SPA | Description | +| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `allowIceFallback` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Allows use of fallback STUN servers for ICE if the user's homeserver doesn’t provide any. | +| `analyticsID` | Posthog analytics ID | No | No | Available only with user's consent for sharing telemetry in Element Web. | +| `appPrompt` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Prompts the user to launch the native mobile app upon entering a room, applicable only on Android and iOS, and must be enabled in config. | +| `baseUrl` | | Yes | Not applicable | The base URL of the homeserver to use for media lookups. | +| `confineToRoom` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Keeps the user confined to the current call/room. | +| `deviceId` | Matrix device ID | Yes | Not applicable | The Matrix device ID for the widget host. | +| `displayName` | | No | No | Display name used for auto-registration. | +| `enableE2EE` (deprecated) | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Legacy flag to enable end-to-end encryption, not used in the `livekit` branch. | +| `fontScale` | A decimal number such as `0.9` | No, defaults to `1.0` | No, defaults to `1.0` | Factor by which to scale the interface's font size. | +| `fonts` | | No | No | Defines the font(s) used by the interface. Multiple font parameters can be specified: `?font=font-one&font=font-two...`. | +| `hideHeader` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the room header when in a call. | +| `hideScreensharing` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the screen-sharing button. | +| `homeserver` | | Not applicable | No | Homeserver for registering a new (guest) user, configures non-default guest user server when creating a spa link. | +| `intent` | `start_call` or `join_existing` | No, defaults to `start_call` | No, defaults to `start_call` | The intent of the user with respect to the call. e.g. if they clicked a Start Call button, this would be `start_call`. If it was a Join Call button, it would be `join_existing`. | +| `lang` | [BCP 47](https://www.rfc-editor.org/info/bcp47) code | No | No | The language the app should use. | +| `parentUrl` | | Yes | Not applicable | The url used to send widget action postMessages. This should be the domain of the client or the webview the widget is hosted in. (in case the widget is not in an Iframe but in a dedicated webview we send the postMessages same WebView the widget lives in. Filtering is done in the widget so it ignores the messages it receives from itself) | +| `password` | | No | No | E2EE password when using a shared secret. (For individual sender keys in embedded mode this is not required.) | +| `perParticipantE2EE` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables per participant encryption with Keys exchanged over encrypted matrix room messages. | +| `preload` | `true` or `false` | No, defaults to `false` | Not applicable | Pauses app before joining a call until an `io.element.join` widget action is seen, allowing preloading. | +| `returnToLobby` | `true` or `false` | No, defaults to `false` | Not applicable | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. | +| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. | +| `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. | +| `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | Not applicable | The Matrix user ID. | +| `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. | +| `widgetId` | [MSC2774](https://github.com/matrix-org/matrix-spec-proposals/pull/2774) format widget ID | Yes | Not applicable | The id used by the widget. The presence of this parameter implies that element call will not connect to a homeserver directly and instead tries to establish postMessage communication via the `parentUrl`. | diff --git a/src/UrlParams.test.ts b/src/UrlParams.test.ts index 10f1386b..092b51d3 100644 --- a/src/UrlParams.test.ts +++ b/src/UrlParams.test.ts @@ -7,7 +7,11 @@ Please see LICENSE in the repository root for full details. import { describe, expect, it } from "vitest"; -import { getRoomIdentifierFromUrl, getUrlParams } from "../src/UrlParams"; +import { + getRoomIdentifierFromUrl, + getUrlParams, + UserIntent, +} from "../src/UrlParams"; const ROOM_NAME = "roomNameHere"; const ROOM_ID = "!d45f138fsd"; @@ -195,4 +199,48 @@ describe("UrlParams", () => { expect(getUrlParams("?homeserver=asd").homeserver).toBe("asd"); }); }); + + describe("intent", () => { + it("defaults to unknown", () => { + expect(getUrlParams().intent).toBe(UserIntent.Unknown); + }); + + it("ignores intent if it is not a valid value", () => { + expect(getUrlParams("?intent=foo").intent).toBe(UserIntent.Unknown); + }); + + it("accepts start_call", () => { + expect(getUrlParams("?intent=start_call").intent).toBe( + UserIntent.StartNewCall, + ); + }); + + it("accepts join_existing", () => { + expect(getUrlParams("?intent=join_existing").intent).toBe( + UserIntent.JoinExistingCall, + ); + }); + }); + + describe("skipLobby", () => { + it("defaults to false", () => { + expect(getUrlParams().skipLobby).toBe(false); + }); + + it("defaults to false if intent is start_call in SPA mode", () => { + expect(getUrlParams("?intent=start_call").skipLobby).toBe(false); + }); + + it("defaults to true if intent is start_call in widget mode", () => { + expect( + getUrlParams( + "?intent=start_call&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo", + ).skipLobby, + ).toBe(true); + }); + + it("default to false if intent is join_existing", () => { + expect(getUrlParams("?intent=join_existing").skipLobby).toBe(false); + }); + }); }); diff --git a/src/UrlParams.ts b/src/UrlParams.ts index e0aae237..423235ae 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -19,6 +19,12 @@ interface RoomIdentifier { viaServers: string[]; } +export enum UserIntent { + StartNewCall = "start_call", + JoinExistingCall = "join_existing", + Unknown = "unknown", +} + // If you need to add a new flag to this interface, prefer a name that describes // a specific behavior (such as 'confineToRoom'), rather than one that describes // the situations that call for this behavior ('isEmbedded'). This makes it @@ -142,6 +148,13 @@ export interface UrlParams { * creating a spa link. */ homeserver: string | null; + + /** + * The user's intent with respect to the call. + * e.g. if they clicked a Start Call button, this would be `start_call`. + * If it was a Join Call button, it would be `join_existing`. + */ + intent: string | null; } // This is here as a stopgap, but what would be far nicer is a function that @@ -211,6 +224,10 @@ export const getUrlParams = ( const fontScale = parseFloat(parser.getParam("fontScale") ?? ""); + let intent = parser.getParam("intent"); + if (!intent || !Object.values(UserIntent).includes(intent as UserIntent)) { + intent = UserIntent.Unknown; + } const widgetId = parser.getParam("widgetId"); const parentUrl = parser.getParam("parentUrl"); const isWidget = !!widgetId && !!parentUrl; @@ -243,11 +260,15 @@ export const getUrlParams = ( analyticsID: parser.getParam("analyticsID"), allowIceFallback: parser.getFlagParam("allowIceFallback"), perParticipantE2EE: parser.getFlagParam("perParticipantE2EE"), - skipLobby: parser.getFlagParam("skipLobby"), + skipLobby: parser.getFlagParam( + "skipLobby", + isWidget && intent === UserIntent.StartNewCall, + ), returnToLobby: isWidget ? parser.getFlagParam("returnToLobby") : true, theme: parser.getParam("theme"), viaServers: !isWidget ? parser.getParam("viaServers") : null, homeserver: !isWidget ? parser.getParam("homeserver") : null, + intent, }; }; From ba5da7e9af13a71d45cf9ee33a08523194e23656 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 18 Dec 2024 15:31:45 +0000 Subject: [PATCH 63/72] Inform user that their camera is starting in Lobby (#2869) * Inform user that their camera is starting Instead of just showing a grey box. * Review feedback * Show spinner from design suggestion * useMemo * Lint * Lint * Feedback from review * Use colour that actually exists * Refactor into Avatar superclass * . * Remove size limit behaviour * Add VideoPreview tests --- locales/en/app.json | 1 + src/Avatar.tsx | 2 +- src/room/VideoPreview.module.css | 11 +++++ src/room/VideoPreview.test.tsx | 73 ++++++++++++++++++++++++++++++++ src/room/VideoPreview.tsx | 37 +++++++++++----- src/tile/TileAvatar.module.css | 20 +++++++++ src/tile/TileAvatar.test.tsx | 27 ++++++++++++ src/tile/TileAvatar.tsx | 30 +++++++++++++ 8 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 src/room/VideoPreview.test.tsx create mode 100644 src/tile/TileAvatar.module.css create mode 100644 src/tile/TileAvatar.test.tsx create mode 100644 src/tile/TileAvatar.tsx diff --git a/locales/en/app.json b/locales/en/app.json index a47e5beb..0e71fd4e 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -195,6 +195,7 @@ "version": "{{productName}} version: {{version}}", "video_tile": { "always_show": "Always show", + "camera_starting": "Video loading...", "change_fit_contain": "Fit to frame", "collapse": "Collapse", "expand": "Expand", diff --git a/src/Avatar.tsx b/src/Avatar.tsx index dcdead7a..a76afbca 100644 --- a/src/Avatar.tsx +++ b/src/Avatar.tsx @@ -33,7 +33,7 @@ export const sizes = new Map([ [Size.XL, 90], ]); -interface Props { +export interface Props { id: string; name: string; className?: string; diff --git a/src/room/VideoPreview.module.css b/src/room/VideoPreview.module.css index 89422af7..eeb9276b 100644 --- a/src/room/VideoPreview.module.css +++ b/src/room/VideoPreview.module.css @@ -25,6 +25,17 @@ video.mirror { transform: scaleX(-1); } +.preview .cameraStarting { + position: absolute; + top: var(--cpd-space-10x); + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + color: var(--cpd-color-text-secondary); +} + .avatarContainer { position: absolute; top: 0; diff --git a/src/room/VideoPreview.test.tsx b/src/room/VideoPreview.test.tsx new file mode 100644 index 00000000..068ad050 --- /dev/null +++ b/src/room/VideoPreview.test.tsx @@ -0,0 +1,73 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { expect, describe, it, vi, beforeAll } from "vitest"; +import { render } from "@testing-library/react"; + +import { type MatrixInfo, VideoPreview } from "./VideoPreview"; +import { type MuteStates } from "./MuteStates"; +import { E2eeType } from "../e2ee/e2eeType"; + +function mockMuteStates({ audio = true, video = true } = {}): MuteStates { + return { + audio: { enabled: audio, setEnabled: vi.fn() }, + video: { enabled: video, setEnabled: vi.fn() }, + }; +} + +describe("VideoPreview", () => { + const matrixInfo: MatrixInfo = { + userId: "@a:example.org", + displayName: "Alice", + avatarUrl: "", + roomId: "", + roomName: "", + e2eeSystem: { kind: E2eeType.NONE }, + roomAlias: null, + roomAvatar: null, + }; + + beforeAll(() => { + window.ResizeObserver = class ResizeObserver { + public observe(): void { + // do nothing + } + public unobserve(): void { + // do nothing + } + public disconnect(): void { + // do nothing + } + }; + }); + + it("shows avatar with video disabled", () => { + const { queryByRole } = render( + } + />, + ); + expect(queryByRole("img", { name: "@a:example.org" })).toBeVisible(); + }); + + it("shows loading status with video enabled but no track", () => { + const { queryByRole } = render( + } + />, + ); + expect(queryByRole("status")).toHaveTextContent( + "video_tile.camera_starting", + ); + }); +}); diff --git a/src/room/VideoPreview.tsx b/src/room/VideoPreview.tsx index f9609b99..e2d8303f 100644 --- a/src/room/VideoPreview.tsx +++ b/src/room/VideoPreview.tsx @@ -5,12 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { useEffect, useRef, type FC, type ReactNode } from "react"; +import { useEffect, useMemo, useRef, type FC, type ReactNode } from "react"; import useMeasure from "react-use-measure"; import { facingModeFromLocalTrack, type LocalVideoTrack } from "livekit-client"; import classNames from "classnames"; +import { useTranslation } from "react-i18next"; -import { Avatar } from "../Avatar"; +import { TileAvatar } from "../tile/TileAvatar"; import styles from "./VideoPreview.module.css"; import { type MuteStates } from "./MuteStates"; import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; @@ -39,6 +40,7 @@ export const VideoPreview: FC = ({ videoTrack, children, }) => { + const { t } = useTranslation(); const [previewRef, previewBounds] = useMeasure(); const videoEl = useRef(null); @@ -53,6 +55,11 @@ export const VideoPreview: FC = ({ }; }, [videoTrack]); + const cameraIsStarting = useMemo( + () => muteStates.video.enabled && !videoTrack, + [muteStates.video.enabled, videoTrack], + ); + return (
diff --git a/src/tile/TileAvatar.module.css b/src/tile/TileAvatar.module.css new file mode 100644 index 00000000..fa05c552 --- /dev/null +++ b/src/tile/TileAvatar.module.css @@ -0,0 +1,20 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +.loading { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + opacity: 0.5; + /* TODO: make this --cpd-color-fg-primary when available. */ + color: var(--cpd-color-text-primary); +} diff --git a/src/tile/TileAvatar.test.tsx b/src/tile/TileAvatar.test.tsx new file mode 100644 index 00000000..ae5ab610 --- /dev/null +++ b/src/tile/TileAvatar.test.tsx @@ -0,0 +1,27 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { expect, describe, it } from "vitest"; +import { render } from "@testing-library/react"; + +import { TileAvatar } from "./TileAvatar"; + +describe("TileAvatar", () => { + it("should show loading spinner when loading", () => { + const { container } = render( + , + ); + expect(container.querySelector(".loading")).toBeInTheDocument(); + }); + + it("should not show loading spinner when not loading", () => { + const { container } = render( + , + ); + expect(container.querySelector(".loading")).not.toBeInTheDocument(); + }); +}); diff --git a/src/tile/TileAvatar.tsx b/src/tile/TileAvatar.tsx new file mode 100644 index 00000000..bba826cd --- /dev/null +++ b/src/tile/TileAvatar.tsx @@ -0,0 +1,30 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { type FC } from "react"; +import { InlineSpinner } from "@vector-im/compound-web"; + +import styles from "./TileAvatar.module.css"; +import { Avatar, type Props as AvatarProps } from "../Avatar"; + +interface Props extends AvatarProps { + size: number; + loading?: boolean; +} + +export const TileAvatar: FC = ({ size, loading, ...props }) => { + return ( +
+ {loading && ( +
+ +
+ )} + +
+ ); +}; From 6d5dc0dfb77823fdf8d13c1536d027a4d15f636a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 18 Dec 2024 17:03:29 +0000 Subject: [PATCH 64/72] Fix loading of matrix-sdk-crypto-wasm when running in local development mode (#2915) Fixes problem loading rust crypto when using `yarn dev` --- vite.config.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vite.config.js b/vite.config.js index 1feb7d66..7f088801 100644 --- a/vite.config.js +++ b/vite.config.js @@ -109,5 +109,12 @@ export default defineConfig(({ mode }) => { "@radix-ui/react-dismissable-layer", ], }, + // Vite is using esbuild in development mode, which doesn't work with the wasm loader + // in matrix-sdk-crypto-wasm, so we need to exclude it here. This doesn't affect the + // production build (which uses rollup) which still works as expected. + // https://vite.dev/guide/why.html#why-not-bundle-with-esbuild + optimizeDeps: { + exclude: ["@matrix-org/matrix-sdk-crypto-wasm"], + }, }; }); From 7d00f85abc85706f1dc93118bc5c722c0e65788f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 19 Dec 2024 12:37:10 +0000 Subject: [PATCH 65/72] Add screen share sound effect. (#2917) --- src/room/CallEventAudioRenderer.tsx | 15 +++++++++++++-- src/sound/screen_share_started.mp3 | Bin 0 -> 17323 bytes src/sound/screen_share_started.ogg | Bin 0 -> 16737 bytes src/state/CallViewModel.ts | 15 +++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/sound/screen_share_started.mp3 create mode 100644 src/sound/screen_share_started.ogg diff --git a/src/room/CallEventAudioRenderer.tsx b/src/room/CallEventAudioRenderer.tsx index a2a0a7f1..97270791 100644 --- a/src/room/CallEventAudioRenderer.tsx +++ b/src/room/CallEventAudioRenderer.tsx @@ -13,8 +13,10 @@ import joinCallSoundMp3 from "../sound/join_call.mp3"; import joinCallSoundOgg from "../sound/join_call.ogg"; import leftCallSoundMp3 from "../sound/left_call.mp3"; import leftCallSoundOgg from "../sound/left_call.ogg"; -import handSoundOgg from "../sound/raise_hand.ogg?url"; -import handSoundMp3 from "../sound/raise_hand.mp3?url"; +import handSoundOgg from "../sound/raise_hand.ogg"; +import handSoundMp3 from "../sound/raise_hand.mp3"; +import screenShareStartedOgg from "../sound/screen_share_started.ogg"; +import screenShareStartedMp3 from "../sound/screen_share_started.mp3"; import { useAudioContext } from "../useAudioContext"; import { prefetchSounds } from "../soundUtils"; import { useReactions } from "../useReactions"; @@ -38,6 +40,10 @@ export const callEventAudioSounds = prefetchSounds({ mp3: handSoundMp3, ogg: handSoundOgg, }, + screenshareStarted: { + mp3: screenShareStartedMp3, + ogg: screenShareStartedOgg, + }, }); export function CallEventAudioRenderer({ @@ -89,9 +95,14 @@ export function CallEventAudioRenderer({ void audioEngineRef.current?.playSound("left"); }); + const screenshareSub = vm.newScreenShare$.subscribe(() => { + void audioEngineRef.current?.playSound("screenshareStarted"); + }); + return (): void => { joinSub.unsubscribe(); leftSub.unsubscribe(); + screenshareSub.unsubscribe(); }; }, [audioEngineRef, vm]); diff --git a/src/sound/screen_share_started.mp3 b/src/sound/screen_share_started.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5a4113f98340b222880bae8f8a96f0314bce41f1 GIT binary patch literal 17323 zcmdSfWmFXJA29r(mtMMI>7|!$Sh|+(Ub-6;5OJ68?(RmB6a;Am1f&rJ1*Ak#QUUqF zqyOjJZ}0c_oO7MYb7szGzVp3mv{Xg00XP5vtbYp;1_TH23^OD(v9PfCyHEJH{BKiO z7$yjN_><%O_rU*k(eicm`+EWD-@*?7DkuO4hmeqrjE07WiHVI33KbL-6cdw?fy33* z)O2(V4b9EXZEYPL-Q9hCeS?F;!%?W%*x2Oc%*_1!{L<2@s)mN=&!2a8_Vo=7jgODN zd$+K#wzjslwYz(C^y9~m%gfu_hljuSl13=$s)-5;!(bRq|L6HAQ5;z5X#jxw_Z&D8 z0RI0u@&D@jufHk)V1h}8OJPHRJp9_T4jUkf$d~KwL}6qT*JDYY0st5Q<3|VZ-(T>I z!2lqNEpF{aq@R`Wk8NCp|9nL6?K0`$pKvz+2t~Z%t7vr8_DxVB9KnKjzJ2}d;*J9E z;RgWFf-wL?0c%_WibUs26hb3qdl)ImtkSc}GK@!q&14}3V!V5$DEJf}M}jMR`s}}g z(tFy6?P~=VY#I1B{SL^u*35Ft?9p6}|GC-w2m@GYebJO~61pT&$-?@pG`kN@jM10@ z6mJ3TRHYKBbVln;9PMAO&S9{D+y3mmEJ>kg?lQxVXLlOn9~?!ZWmbx!(dc3L)05Lk zZ;$b3R|EyY)KEqkG-j)TJv4_zFG&~)8%17lVa+(=i@^A$4!~IG;W5WGAVD1qP_4Kz z$q>T7HM=l#3TRk ziU`0&VUFZSAD>wcVGsEwFSA*2B>eLy0F88$&#$=;0cJ6MSSPdqf$C3***0Azvs^Bm z32KAkIM{v;)0U3KUDr*I> zaHyau3kZlx>ppSSYW%*t(n9&=ar8I*E6$9^Z|QK5M5u%uDF(zN1Lq{6Y_25%gzV4YNH-lkJ#AeKW9vw0KBa&UJh z#VWHM6dH>l^*$7Rs!^#a_uBmf=P}RFKlbqAc%<=Wc>pDV)5MUtOM%zqne?SeUwQxS zCpwzwg+1!v+lrL0em~jqHBzvxc;tBi!fmP;1pSB4OH~}8q%ReRA^-e|(-xmX5z&QO z1~x8J@W*Zn+B1!Uf*hS_6Y5&E&62bEjJ-2jO|d6&z7Rl1?Ukz*3#>fQbvfoRC|P};JF-Pt zmv|Z>2lgd>)8>({x6Ws+PC{e4voTkStBKQkWMh}y)b>_Pnd4&!zT-Loo>=N}0%N(3bp#s?l^%-?%=;Oq zN-&_14WJzAjCqHY@E&n~HWijtD3^MjU`kl4nN!$T-u|&sHccO=#Bmm12cqmqQbo9+ z*>4K#wlh30^vuigKK82C78hxyI(Ioh?rfZQG4RF%a88JqCJdF0=f*Jaw4**hj@yn; z!L0Qx9$@^J|4Gx^--=I1zqUBFab33v*teTbS%Z=s9Zsod*E-CU@}6WxDW7z=htKWt zf?2JgrK&iGY#rFXTIM+Si0|)br6fN*cH9(dc=PS5$x{2pHxGeXPkYm=HxH}xz~5Sk zkvD7xyv`t=4<7lYVu(p57oy;h!OoAHkG(LUs(}ElBQA#pkQ@gw3Xq{^M{%>p3WyiR z^@B?4-_mhjUziZG?LNjbq2~}VP*+ip`_d%Ssw;M4k`uA{c1er?j8qSe1_c1cb#9me z3zUZEB#G>8!~K4hRGW`{lG!pnM#>hidp>r2S`yj^vjdE3xin7VbQN_~jiVyzN3JfQ zJr~{QUfR^3jMdWQ&we`;&~orSM^^JJz#F$6ew`A2u!T7yG*Q0__({=%Ua8h1aF=3r zbz9B|hlilXE_=LSY&;YEPU>y?2#pHqHHmx3hjqkuZ$Ell43ZqA87r`GUR1GWCrT)b~XXIL-mld`2&fiV#hLi1Hb%U$7pxa2JbCmo*M(i- zU~;r~w*s3eo2Ubu5THYU4J$!eeTKocacJ8r!B(-Cnlx^jN0z6io0LY{?hPh^WIt>4 zOYJxGzcQdVA~fd0V(Y_(xA2x3aAT>KBaqi~XWZo27w;<*|KK^|LoH(xsy97HpNwJCz>MYt4s&r_9`B?aDChbdn^ z{gSGYBVbnAtfQ9$5Mc&ft76bngwjetNHDnY23@bMXaL4O;ffGJ`{W*VpfEFQ##oXqd_4omx`k$B?346UqJ@5?U|D z(%pY-J6WYnt=uI3pqX*<5>UHfalF;J2v0ts8oA7%xDgsADX_OFQzv1=_sB^!;E=yat1#R;pBPr)~}%@9h)|1HN^?u`tHD8Ol9d~MF+#lrdDI0;%# z*?%p*4lneWV6X7-Sz2b($Ca<@o+if)XT41dBTc}t(%i5sn2C1Bd|Q_Dr(LO|aC{83npJUY#V4(*0DebOG#kar=ZI=WP%H@Ym1`X3F#q$u%lGIL1FLv`0tr~s(-sW)0tKKVz zu@AXNk6R!AijhD!&8aaNJw2#2#4f+QC2NH zTqY2vjVnpo8#7g9O8Bd1qP((5V zDSp5Bbh0U}Tbe{1**fwt-DsDe#Qq#qnOg;#yAd-tjtfa*ZMwSAw4Ez@|I4z)Ni4%t zDoy89ZV!mdPUOf`2aJ1HjO2J#7b!DUb{owoBaG{uGEG;-$Upc8xqU#Q)|Bm5tWnsgZ^Pcmk%B%THi5SX!+@h|V5 z*uHN%{F&0NO^#jkBa=2y8~rVN3;B4csh`DD}e z%i?Jf16c*xu2shHLT65=cdx5)A8T29S=ygwRuP3GB^S<$k!ykK)nfn`p)D_p*0jk5 zW;)B0%nSlP8RdL`Ga;+bCc2S6XyxEPCbwgGrUSGjVE1*a*K@hGTMF5Vn>VxnFaDWO zBUTE_PC`82b=0Lte4>K!Q_AgII1J8U*#}eol%dnD-+nY(bs3w*W8KC>xyRM=h9YH< zXq$`J`Xm$zwS1hDx&$v1-D={(i~ zZ39nw8~PGzw}$x1?x0wIR6zsdQn}sr)6bwky8>bAWDu*4Lc?~n>@O{vsGUX(rLJso_j_G;A2>T@K?f2p<$K> zV+FijG0Ik+*o=6sPw@W5zZih&F16^TLouoCI!))n>5}*=Sz4gKWmx#{#}t!WhE(Z) z^;=t{rRj?$)`vK@&|as_Fk#zc+#HkXF~ke=)%fb4uH|&5l#Z?YbmUf9NLnx^A%@(g zYVaNce-<1^M@j3yB%g)G-H3VBfO{%4G#T%3?x?#!n^nICXI~bt5(=)+-KU&j@QFXk z3Cs2TP&rhcuobCyHDKUZHkql-)4deX1#nAjG*#)xghyIWH2N~<%I6-j42K3}+i59z z!MeIw5)ar#q@lf?&ED2Ba_`-ybg9zz1;r0MIq9wB%@NY5)N7J^SQmghmPgr!nYUb3 z2M@FI2pmc_a%^9tB|6qFBou#Ph<@eDMah+URisu&v`Q8g;bRwTj`LaX>zh^CmBJBO zo{gxYJ3~JA{NlvEyT3i~9Zww&B;`PGlZ60Ht2aw_2#QAz7+L;ToqYuP4c=lbcZO(bW;X)t1>5cC|p7)T#fcvK4hM<0)u|Pqi4T zsXv54ulxY_D}C)*3(%iFQ4<})rk67^Nr0mJ4zto({8;|`ncG>RjCt#HOzJ?VBq>`qbCiFW7W+$qR*{nbBCoXZL4$vuB|a4F zMo|Hn*jd{8m<1nc={@2KV=>|;urnDuoiSSwkHKtMYI^;RkpHZ=ii++w@*UY#3@(U0 z2#IlICP!s;~-m>SJ_6h^-=aYN= z$TEq=t~Uj#U_{tEhQuBW+k5TT#kqw&@BBpWOJ~~@#mG&KX`hlVOgZDK&@J0MM0f>k z(RcCq`!L$AMY5hO&!&F2e^Y%FlOWCPiyDN0>Ff&ew!{Ko z-=O>O!f{3C{YY!+aT_h;?LvKq_V)p@! zL&!PxjkH=LfJJN8kH6%D$tCqQHr&SSjX@rWO={z?JLPj35-FG{IKA*0iy7(E48eLM zWD-{K?%ncQ!*iw=vdF1>&(2M{*FAPdVWIrSA%BNwnr^pRhwxX&o1#g<>h0^~TkV|; zEm{?)scuuKv zV71VPQcC=gZcLuUs+SE3Ex+ZnZ+|u3rX(gv)){Yvdg&vW{N%Oo2-2p(zBj=MJrPVk zS&hFIO1lTP+_Wqo)dDQdo)WHYj#63fdIkkWye&yiqbfto@JjWIO~Sk;14|E6TE4Wp z*k0pq$?2daC5+WP+jnoeXAbK1Z|>b#eEo{22eUcIH+`p^d;!A~C7$E*H8wpTa$Ij- zZPEIJ=f;^?p?B!MQgMVR(TKXPE-9NKKk>gC!%V&YE^=@Dgm?6{T#Gp)D-MR1GUa? z^&-^CjLEPw55x4cOErkrH4X~%fN+h9$=rrM-rOS4&hc3|Pny%dl}iAySxo_>SWPI2 zqljw)zcX7LZUzGTD|uYO^}uT=nbR+?>Im}RTHF%r^^%Y9AHTfR(9ue;zto?ger1LR z+up=JkC`{i{Hh!*I%&qv4Sz$@o~4S?$z12~8N%;@*6O>UtO&`I?zl2PXn@kfH_XbF z`|*<5dT?+Vx4bwerwg@>o;I*ZdkcK3avC-G5#yOWY4MX+EPbv1_OAc)ycAaQ-_L5E zndYDW6I9ewNrNw56c)x4iM`$JWT z8=vOr1IMRUNQgp^2Jb!H;q%@Dr-HX^f9V>Rc5F|}4jb}^;x8)brZnHIklxA#+a zY?X(o>IsX8f$4bilF7<)j!ZAS;oQD)`-u{5PV;g55X?9?sAt(RnqYJr6Z4woD1%lw zv!HlAwV)v4Y?y(RlU2&%a%v(ahE^+6auKs%WM)|=(vWTHx7YJ%%nECgIvy>K=c*K3 zdA`0Yv+Fg;j+0atmYl~Ss^wRW&5`1lH)M{-<5ho_KRoEwkC1U?iv}glazPqB8u;8@Nx4F1~(MQ&)V> zFZxxao0~Isi*J-VpL+uxIN1UHL$yMrFD^9Q?n?l5B{Lb)bH3*6DeJjg>RX}I$=FXy z_x7Tp8qryzhmwu?5QnT^8vX!CupwKE3PbGsJoF-KIxgXGT4#RIRrO`xC%sG#ZH9{r z1xoio?Uo=;+?OQNob&GDY~Zb+&;@%RU$IZToQv#sistEia#a8W zX69u$j&SFa0={U(*{?D6GJOkZlVBpRqVQ)<02eohJf@nnNl?y3!yQ*@XC_MdkcT7) zx4l>UYK@YwRqSx z{6ALQ7(bzm!G*rRZSwE}`y>m|oC+PzS@z9hv)bd}=4y|#iSy!vcjgIAc2I59{7}uM z3B|`!zjeH6~n^{0+=LgvDB@N&fW`;`M~>HW_Q`Ihan zCaImS-~a44nY>QakO$!wgrw;jSsorDWMxmT{ygk!_V8NMj5h&M033>?@}BR~AxkSo z{Xi_d;);({cO%xI%EqJ>e1grBt<|)Dk_A4G()8$#9zJ7SHGpbZlE~WJ;`_SB%OD$oM{#?Mr84J6edD+n7$~w{R%&@zMZHt{0kK`P+m^vM?vhg}tm)2dHuCbx7&cT61FPlj3_A8Bq zy|hnb8XKtn$oS^I)Ry7L085FJKJJ=vW!5*KkHGgEIUe15Xhy<|^@`MNcCNyt3^Wc7 zl7`Jl@xNk0Pqv?iZd7BRUYaR8Xe5PoOIkX>e_`!PWb)p5Xm-@-81h7aJDt)Rgt}GU zjz`j_eyl+Kq?fs*z30$V61n7z{pW9q8i|q9m-XY>_T%O0@YzqokH}Qfm8(}8{F}|+ zHd%B;SbxC_sCc-=P<*fe$bqbq3gU@IJ%?!8oC$EjW3GBu1up`OJxj`8an9uHUt6fC z=c)ccCLUE7JZtEoqf6MGE3tS?f1RqdW3Y*6oD!h{3dzhIgGDmnAO`Dsa01aQ1a5wc zl8VI945ghpHb*p8^bAQ6o-mG*8m4pv9L}tnKPMfT;SB8+>e>@SY_JHr4kg=B3T2ip zy*@C0py|ikw%Nkzk%Tb%?v}IpL7`>~cM;ZQLK0=0sl5|-Z_L$uv?9zOF2w&%Qnr$7 z2?Bbn3emfii=D}?eow~>9E@{|26o_7_^e{0tbS2fwp3L!{dCf*!LdtpYsRA6Q?J$h zbpPCx8%E9}{`)wRPaIJox&Q#!HEozu6#1WdQM3&_(MbhS9{qmo`7AneCY0-<1LdxoE z@3D_d9{$XKYKRsC4na@%_ z&c)rE_C|I}eiWsV0z>u{)e}aIgH(nx*?q14%4bRPgrQ^Q(-F~!bl=Vudp@t++j9W4 z7@eN8DU@{%8EEqnAGYXF5UX%S!uV3G>gN`|l&A)(`_EHNYCp$HrUK`-Rl0w z;ZF*ZKOuF>Z+_jGZ$9SO3>b0vylXUQPFl`RDohfs2s{pL@MHF6PWbgP0&88@drBKHc~Q7u)!dcA^pv>P z87_eizI2%+-!e$ePgZ;_kB3m(g-g(-i{0PDMvGcob`E#;B7`F*VouKa+)KH3F z*a*hDVuRg}u7_vpGdG0aPhiu+O?23L>i)Lhs8dMT8MQdNL8O@dxCIofFfy!i#kSPp zn3#%CnZbi}kqCv8_^@s`15{51Ny{9_>beeG-Ru^7>KuPE_{sa9#bC2ll*$d|ob<(; zXD=4sSkDjU`mrJb3~@RS9sq}Lf1>4|zZEdzD602OpB!neO}FT$Er_48uF}%xy<^FK zh|^OVh;WE+%V=w~4D-llHAGmp;*WGGp3AhhoX5vEi5Zb7rs^#F9XHyKR6b=4{1rIc zX9i{^iaf9{72MFK7$Z57K$ETQEV?W)f`u7TM`mn01&yF1VbeB>^pe0+Nz{GgjM=ub z?CRw_EK+W>M0?cd!{?BTVae$V{mzEI_1M0&t0absFV_|C2Xk+_4sY-8TYp^&WKJ~! zsza}f|Kq?Apw-PJcId|4w2F_=tC&k<#g@2DukOQixw$|jejiYSSBtwCUBnA+CwyKu zzC?a;AvJ!HyLC&T-RUi;9x{!1mCm>UXB#Ls07)ebnWo?Za#RK|Wh0kg4i2L_nQpR% z=MAqhk~cdhNV$F<=UNu5wxSt`*fV<{s$_Y8HR-k9y}bpU_mD>i6Q2tA74NXjo|erX z`}KVM;w&OvA(q*OGuHP3I~q|_GD?wZLCi&OfKRPis)o#}d++Jf_0QiiVb_rZ(Hv$u zZ=rcJ?BiI6kDQehC^1qpWN{ttX)LgBdv54BVW&O?Fq`hGpxH;Go|0UIRCqlT!sBC0j=G*rP@$|jyJTI?uy^*%`sPK)k z6|sja>KhMEI6r^PT`DVsjRDjH>{qTbs?aey;nU$mYm3T!U~FX4kZA|~I!aF=d$R#T z(^4bN;{Mawo>~9Po6I+oQThb`Ve~X#g6=nJG(Oq!k(9UHV#iStWB%s8=0e!W%t#IPPj0T3e-uJ$8daqv>FzeZP#uE^Lz9)_sQUJH>QDc9Y z#iYWQ=KAOFn-HbXtGlF6F{E=y_uHc?j);c}DkyilpvB6K-&JOCn9N)q6=*8SX4ok$ zHUD1z`^AIenEJy5RX)TCGhfM~;{ z3vNX2m$b6QsXk7mykb0r3@)_haQqkEP@dP!c&*B>_xSU-UpIbIOdO@K27)8$Lt zxZKoysfCnb?;16M&G~VIJR&-Voj7U7qZy@&xuWMe3u_0@XSKXHi%(p~Q?~A(C?=iD z$Z-`@CaYfmz^ebjk$1o{D;6ynvp@LxON-TGfwBiS3+K+Qyfp%#WHT}UST(DOe#6}E zXTs2%aMj3cLow^koZiLjaDf+RIMphpffq$^YNwldr59nCn7U3Pl{lY=Ngwzk+LGpe z3g?)geNsj@j9N=>PlR|$XF%DJl*y)AyACQ$mBM?5{4qQJBzhhRp$K=j^4uxR?y}9U zdhOvObmm&FkW?>TW3*hw10NYQL`}4p?VtbS1kCrM+RdRd{+66g0}kJG6Orq%+3iCm zg~GbTp4j32{LJZe@HY5O851`CBr6^v>Lw!U&Z-1mM0aQ2d7$}NOS0$x{3DY_90lI- z{jGns#v9Roc$)aIDZ$pi{sIhKI9*A!w#yA>RJOstUP^?6qAvE-B|k;EbF ztkxP04eWN&Hy>n~p!xNj>{JZ^aM%D`3@Le$Mm&mQa#yR+5NiJY0*nAvWqpvqLH>H= zYU>G3xe=s-Hvp3is*3=Z`%d$J5K6 z>D>0J5;N-gcL*Q`4@fO4Xv21fb8*Vx#ADS4jOf<{Fk60X)A79dv8t!aHy9K9 z)naIw5-BP-yR6boH15eb_-K!%di;Fi=lkXOv4V*+ZtiZB31Tu~7KjFLbQzJ-+8JHm@E=o$ z();}2Dr^F!fByNHi)NyTFX7NrEj+qRAD6g9_jMtG?b>0%LYLv5M1Q{>B*^(CMsAL} zkP%Adc(ZLpGiQp^L)0XMT(%o1$q=_gvQr+0DX9QfTR3-HhggPR)Sto!HRXe4qPmX| zJyL^nA!0j=6J}FbkVsx=v8#lEsGO`Et}ETF@@}+Jt)R{ zP6NpDFYfyI@;Zw-*`zylhkGHn%8xui5Aq-3J35m|;aUsyo1=;&vO1uc(VDj5B3X-?#(+*SRjMco>&j?iG74$k_i>q=T_n@ z;@tV$|HWydmK4-z(|*rrGEV8FE-v4x_pH*{>u8UUgEZ~2aq#qHD&6;4^HeEen5jp9 zF3r^blia+CrrL`~(Z|*sE7jj2DcEHtKjjw(6z$RiSbfcj8%1tG*O_9?zLgY-{@+5L z6)psD5CH7o8sLB{PF+8V8x%Nfcf$KFsC^LoZd2^L^D9rn$d4O~vWY&P;iMSZcIfxY ztwJpGzI$QFLjO|@)+^bdKXxhTB&zd30|3?l$Z1=o)zK;-(@ zI(E=Fh&)Qok)8JroKF4<(4c3i`;y-rOT@}!{?#YfyI?%&_J{g@N=eU?r@w#7<3`;q z(+U(yiB%mI37q#YdY-47<)3~y0&u(Z2v7yb{w@nyOhpyZRN%zIjeq_V1X1P!+A5*a z^V)U220pPl_>9dw0yRcxD0OXVPc9VSL_}`=6V<4WPjGvF_epajU-+6bb%L-Lw5EOP z;r{7uaE$5T)PI%>TMnrO=H@<3i|N=&0aB#8KtA+clBmuL#Lz1OHolx3*9$Eu1q5Dd zHBT)mFQf`bhWZl5iKdPoLeSJ;vZQ!&Og*OfwJZmk9abDWH393XKN^lPoPiea=k@p6 z&9{G_yFJ|JMq%t7D4XIHNZUD2@u18OV(4kSDN&z~MWMDh-%p$UT=9rO*pn3-JT^oZ zPaln&PMi?d`&Qk8hAX;;h%g^dDJ?zsE+r$b)*tHmVsn%I$vgN7rQ%ujb7EeK$J)ki z!P~C&T3I@H7!~;Lr&uqfP(u{+GUR9U#0*0jS)i3>1HsZY!Jh$6omWY<27JmQJ;mgF zEl*!SEPA-gv<&j!P>Ym9S)22x@4|Dcm+3QPj&LzKunYdnaC6;BU*vM9BBPoi0Exk3 zet>y84Ke>W|M)q5#Dz}_D_zz=7e`veCn}HNbeNgM!v*b3{bhU>a(if3VFLk?biO

L{mHpMORBTBj!q2B;KrzKn^cwHd!v*OOoMQxLI8pAl zebgJ2Z-0;}AF`df{d1&@;o+B~<$}^XM;>8y?umM|`c&Rw91Hg&jmE%K>s8fLh=g7f z3Tp_b3|VQJj{&cxg|HsdI+CKDh;yt`=l|M(Mg=Sn!-W(ga^K-NYMK9}PbhpdLAI>r zgWssRa+ zxZuDbdc+5Ejmpv#^Mv?MePLGh(E`DP>2Nv=s3~F?=r?4SmghX)B(Jaf<@s}b*URoh zdtb2N+^HeIr?E|)_1^Wm>3K0*-x$rsQ{i3uiM$foVH8E$h+GPYZZnfR&QO78siajg zd!w4dGJt{x8(v^^Vc3=veoZluG_?RO`gOQ|K9tqXed^434Sv<7A$o6{Yf_?n}N^|zd_g1}u%JX7b|_DE;ro56vfzb4Y}X$~6OAD#C)p#Z`E`KO;h=hMV0 zl<_y)Z#WIoP|zM4{2Tvn!+Lpt{h6fn9($tE2yW}Xm$Bm|z*l&gabjm3{S_tt zot0r(x~Y1*!1Ji6UC}QZZ#HLF=k=8aG{!X6qqHdsOZF4vVJ~BqJc`f>i!Bb2V5Byq z6`bw(k#52a*2~c)_(UA%tN8-)ti}aYPOW(eE-@khSdx&Us|-HLPif)J-&ktBe{#9a zUcWdWWGcbonV7ceSYg&;Dkv07715a1SUKDdX+5#Q#KfS>r&5P2CGHvzjU9QEMqwb& zGUBAFLuBhBPX`7BQNq7*qt1na=|oMoue9D3sc(~6GTzIb==2zeO-58tOH1=d0T@I8 z51#s3VLHad6RAQ?#LJ1gu-T)JjWL!Q?_9i0p7+kXvb<zChp!}_2j(j{ap{j97Z1JRpC5>3>iO_n;twk`(b5b2TN6uFNm zbIgmgl%>Y}^>_Y8ij@FU!+P}u4tMEGb>2-(O&O#l`RX-Hk;}1?TH>$WjEGjj!*UOs zdcjeww94eJWh?C(_uFB3;k6udBBPPOZskt53f^0qBB;HJeJ=G%t!COA6R{0aDV^2_ z3kseT#1*G-m?2a%|HWvUv_p#6_Wl^Ki1`Rhec9bywQ#X}Lqx>cF2DBbZJp?;YwR0!l~iJXftg5q|Rgp*w1G=qN}S+Ezz@2&c?fT9`NE<~6e%2=kj??D_w?P2o1 z(wy&aqW<$NT(}xGfW#2Fe{qP(R3~36T(0{U|A)!vr2?9xg_i!7xS;JI4Fti5zxl_* zW!O5e8a>MiC1C|{)%(P`v|_DQaLHm55i2lk1O5HDYD87ar}s0jQoU$P^pZT}En=Ju z^uP-&BZhLBTmt)FGkFuANJ1r9vgUI1sGr^pe}DGC^mhE_cNgu^vXp@jjBN;p%lgM3PzB1nynm)uwQ15#>un`~JMLRRcdUMasYlDo~w-}h01 zoDmyRSo_A)XXmAs=xil#=eX{y_@9qXDY)4k6M%iWF-W?~Ed{q!Gwuy%+QS}u7XZhw ze`NL^fy5IIi5P2i9-ts2Ifp z^`6}AV4=q#s!op8M7wm0p-TE86TMONOyD~75@js*^8pWW;glu4fs#mE-h4&=sNdwe zUJE`h+C3u2+614`jm2Q%2s4grLd=erO5w;tKkV;D-0yfP4?!aIsU;584UbJK=#wjP z9#_0%y`m=i_B+;$h8*`GjHpBQc1zj~`J`a@w}lr$7ZexT6LqP)3j7OiV%?pYo;g7hJ$s%cwtZB&vsOxIW z`?k2hh(3so9Y^{L1e2@2UDoD)&YL1fCpt>(cuGtdVMkLP{$*!`X%3s$lv#KN3&v## zQ#!=5{ySEE1d?nsstIJXXhTy4N;}|zR_}B-YniBgHGewcQ%NQHh!Wg_K>}-6pbZNB zLM61{ZGnUCoE4J&_Mw1v%l;32vi&ns$7gF^@D5|8$*EmqqK+B-A|d*HFb2^z%wUv| z5tB_Bg|Tg0cZ4y&M&KB=Nsc{`v(xf%nI=fmjC5RLKMNrnGL(OhGW8t_xdC(Coe}Of z97f~x(^1?ejMhm|vX@_XYc|PTVh0*=j6h1}qd+rKCi6o|Z?!L~MJjf}H0up^1X@S2 zWq=p?YvK$l_hQ8}4pMTEJWYPqcSfo=GXMNn3C~{$O+*(MA?@lw89}_Bg#C@=&-V|} zp~Y-UGmQ;;@?J;zEq*((?22xKCBslr%Rg`pak=;bMYuJ)a1uwri?1sU3hb8MO#jLqsxO~ zn0W|j@u`Zy6@Oqz0g2>lu(q|qgiP%cJ$(PWd&uR{vyqK3x46ihm*W;PL$#vkM7*7?!u^Q3kcE5y~#YITRKy+?WU=D#`lWm4!iI zaA#hQ$jCZPLx>@$@E{aa^R2fMGIHu=MbM<=C|BTmL!hoJAJe&Tov1NE-5)_&ZV77* zPe6~&T2}6#j`7rdn4|VOXYHf0UWvt4XCgkZYIW5xaMXk$$C5No>4N|Hze`_S;8l(8 z4I6S*pF{=;coMpof=RyaD4-#t6-?$nZ_aLPbuolj<($Kv@`zzL^hgE~>GWfo~fS#Ii2R~jA zpJpb8gm_=<5EkaJC^_OP1(~=>dP)`L@K&!mte`Fkf7e|WepoT$TllsF|w zgpY`4BTDj_@k*N=BT$KYSur*&dNhjC4JqK7Cn)m4&})uxUWh|7ry9nWfnXyw7&`uG zueb<=983TC_mQ6e;8w-z3)}X@rG-D%wIR;wA|N?AOzftGptpvlk885>Uheq{U0ok1 zOMX(88iE4G1KDesnETg_1Rya!~Ee-LD4(;PBvd{1#M_FD_m7OXfoX4 znh1(X+rkeTcEZ%Ju3S%M0qAlC~(ML#YZF|u)h2j(+cJ`r-+|z;IAlr9V^0$ z6s|Lrt|5oBQ;+Epa2xD?$3^KShse(;E;x5<;UYZja;;6|J# zGH}RXktec&_~S9Jw>;?_kNmhF%paB*7e)rK-|evq2B1b*a~ykf#zRAWIam^LP)14s zmxKi~urFfu!T^nA_%>}YRS<%%P$3~1t_ku`lnL<+Mz6YTG{Gu~k{Xw4v{r1o1 z`4ji@W~0w{HH1Wde;lPt+Hx*-DqtqO|NQfbFFXa6qx+J!t(}h`j|2F1_DV?vPY%(B z#M|f@`44o$O%)%%M_UKz>It*q(t=I=#fQY$91j|>aiRQJoK{hO8I_A_pF;#&kCmzABz2Sm=M~w#!2nbQwSBi~sD2l-`SA|ykkbl^6dzT)D_JsHO>GPf3)sT&%w+$?Gihg zbo9G(b``ykWc=`ns!L`3w$b|bCgS1Y;Wr1te?{2zi`0`Ax2|P7na?L4#Y$IVqH^@G&}36y)ay~-R@;R*nCv1$2fUJ z_V5b3XC@YEi{-xlo)(eL9DI@w9c43j5&ago^r4(r6#mLy zYK~Y&CjRd)EV3{T>@QSgy26jhqFK5L#IA8EKrl|285V_*0TjjwLSi1#hj~-E6=IuY z02NBbmlM|1gcA8*!GJPIboUfH&?OCSNpa3zN6zv0r$o7^^yqmMyrun~-Cc&PvR{GMf>LvIk{;ne7MKz z2pHA6o`=htcPNj;Q4hWXHcqn15=>)RPcJ}<7nxD&B7(Q|_lEOm%swALo9CX5HGv%j zdLrDwD_mkHN|YjvWD|CmgF%@8`a375T$)1Vu{NjM{9^I8AN%Ri^7vtzZUCU$G)Lv` zQ$Wn$2$n%AQVSNR)r5s&2^)}Dq767k$%u%pw$IXbC#|m;@=$J3pi>k`6FVkqSS`V( zvV#~SpW=F-5cE@!LS-*VmoUaAoV1|D2JVKP_%3E&WIHFZr!M%zf0|}dMChC=ksbe6 z?$KsNAmb3oR6b!`g0ZeR&Fm56-Luu=MzN&|8!_j1u_HVF3te1A9`l0qA|T0!TM#Sa zO;O71(tZCmfcuN6w4n}(ecTM5kH+9}8j4jMz z)22@?hkjxvocLPgVcmQ!Z&?4k`2X836S3a*!-*L?ykxnUc3x%|HWYkx`|5*BSt$)n zG5Mb?nK{%Gbj1Itu&htf;{Mma+Lr5TpieaW|MeSWPYa}+;cTw|HBH@VN3UkGVg2mx zTbJtp|FhRiFRXd-bfZpc@Tc{_Y|12XP5yfA-qHu@*RB|Av}NoEM$jWr_}etF^16k; z%jamxb?wwWvxkBEZ%LbtU;{@@gCI9>pU0O;r~Hm-YG=*bv)bCU9;oa4kL;@Bdv;2$ z^lmlH%HF!}&v8wy&}p)P3JX0}co<2tG&xLgVBk(E)JRy6+S<=?R*plDgQfS;H8#Pl ztmN~8PQ^TP{`16&K3UMT(*EJL^1kP)M&196SuL6r**-muxwf$R|NrQ-e{ZHVt9;o1 z|4-JeUuQDRnpfz(yilQ4q5i92a`OLw*CH4NS~;8*S`0bdghOx6UL)D-vL>nbs(!#K zAC{}p|Nk~~nKDM4{{Lk~pvnQ?(Ek6u%!Vttj)len|Nr6Dxqn{OzYco|?Qr?LWcecHqdA|} N2}Z*YrwFA8003%SKhyvK literal 0 HcmV?d00001 diff --git a/src/sound/screen_share_started.ogg b/src/sound/screen_share_started.ogg new file mode 100644 index 0000000000000000000000000000000000000000..1a3f0c1d77f90d5af2a680921e1a98aaac7e8195 GIT binary patch literal 16737 zcmeIZc{o+y`!{~?b2!GsG0!=Q454F4l;IE!N@Qq2D1?lWDPb3tBuOesIx3P*l+XjYmLcgFx#R$R5wCXQq#G=T_y48 z3tFjoJjW`Pq@$l{wIAd7%n~x&>TH3M$>c&e_47^DC&uBjwmQ{kmZ7x6+Z0ENCywB~^9tFyAN?#uFVlXr0fV8D&) zZA`qeG5PbxRK;DXN`LPDllkgzivHZ%e?H#`V6%XxEcD1IXe?`yb}i! zlDXrL-yr}FfP_WSU1w*wA!l z7fPhP!2iW$T+&kD8c%^icV-`Bmq3Q)eFM47jgKNzFDI563}lAXzv|D_sz<47nY;Ki zP`!c77mQuJe^bi$BO5c+7RwIQe|5_gsz#~h1(7KkryrpdXMs<0#^(DdwXrPH_VVV% zvXI55{ye_D{3@F%vY5>)FS~HL@g+)GSAXrwr2JQvKlT1F8PQ9p96!CMQIQWf56sK`7gr>K<8FCCThF!Uyr>B3dbdbU#s3^ z9JN%tm3J*k`;Gd8x<8LK-|svSSj(^Vg0gN!!3$eE|5eR9eSa7XWW+t(?&w?a$ktLd zC(V}k5C5o$_huw1kCbhTE$>ZGy_p%ztCshdozz9q09cR7z7vxB_x)40K}czvFuFB9 zXs`NUXWWLtS4p%}>#^Na(Vx&g9lB@d1DX+5ZxvS}hq;XE3Ok zM)?<+NT3iTy5Y}!+f<~u`Mu)X_v+T;y4E5_+e9rs=sLU;+r4eI|293(536?D>Fsvd z?!V)Lzf*B&$3@SMsp#(ixOa2?AyF7$P3kh$)TOGaefr6Yo9N!PYCtC~uE@k*mE=DC zRK-oHNTZ<+;hyUGW^+DH41TmHeQZPuN*lB1~XYia--1~n2 zZH7C)?f0Df8`ZoXmAuwECi%!k`b63`O4Kby_e9e=jT?=s%L|(A&5nuQNOxBgvlXyK z!aD|(42%^dOD|GK_aDvxr09|x4FwBz1^>w_S`pQ)iMkEMfAas6ANqS;`nVyw|7iqu zG-@a`YA*CoUcG^+`;KV14QY@6@%8`gs}S{NF4SoFAAO;Y4B!3#LD&Cyu>ZG#|I;}@ zL8gv{{@fHqrHhtAf(Qg{6jtO(<# zdQ^N>1Q$3P4e?wIQG+V0ERL?w)Nbzq94USRYy%*@ysvI0Vv%~oCqp?=anlDwYf+z! zfygYM=i`QpWMA{npjd3vwMx(R$ycLUIfx;Dg|*1`xZD@LaP0UU4lpBtY~tQs8U2x2 z%89wRGXAz=@lMyu&S6t(Ze$j6mV7dD^NZB2#tj!ul)Zz)v44v5^S_~vtVL06ho;b; z#THPH`QIM@>GXrWl|$%mYGSTWMt*+&T|RSbQPk${me3;uC$IVB=ih#;NwHdNE_C-U z(&7m7hhA&b9Z*ZVcU-_0KN)&l#Bbx!g5F>7gvFft8^m=1S>$^sG0r8aohp16&nHOF zR#-v6RREZhxuSNjY0DLEP^7CB<3-JuE9Jbm&8Ob5wN(|1x3^SNx}IjcU)6zPt9mPq z*Gx_6ye&ZM5hD6KX5(FJ2~_TTA3AZM9TYimx|S&PjWZ{38+Mf&bz5_1b=pWu|}FZHoba zvil;t5vMLF{*jhSM(*-We+7{L>Rc4?jAd(h^_hXLajQ>;BvKCEb11G#*O2C%xo$_3+ckZQoC>=3eHcuF>S^ORJXr zhIOCL-@pQYbx>mC@KzJMx%#WVdxubYRESV%BT_g;MZ&V7JBxuf{S#iTQ$pFm? z0Wr>`yAi_S-)xilyq6#V-~^B-Na_KW{0PDmgbq|B0Ax z-AG$Z*_t70k)Zz6Q@VjX+{H5PY$QBO)nn+hoHRtTYL2iak5as_t*UB{s3o@odWp*H zEqSuzh0)|(e|$jU8|rz$L6rij$|-;kb)0|&=nk7`kab8!5P*_r=1 zVT&>QV!7JM0|~O4tMv>F_1VOq6TjJn$zN=kV++o)Mc5K-(S@Y?f{viUPL;&|va8Wy zTHi@KRVcXB%C%OfZV#20eUd$Q{fB?U5Id@~%zcHdm~M;ChD7nEDD$c-cIT_Aq}Q3> z>Mf3L2(#U@U+Zf5+t8Z3-3B$;eY2V0ry@seGe{+F8A%fyMDy~047_>U&$%xoY=8dGOV5YP z=X+aSidEM6n+(g6uQs&z4pepQU>j6;T#JtFp6$*L_aCdjdt~U)sqW;jfs&YW-}s|< zE1r(MYn%=KmVIWQrnCxtC|*75G9K)+Qa&X1irvKV?ygt%L3xvG|3_jzS@{KB3)e$5 z4m9n<4Ew^9yzl;eT|oLY+g%yXf137|!1~d<5WO9f)fY^;f-?`0GGYIwTz!5eYv!+{eUo)JY~KbP^{kh|$(GwbsHiz9xm-CY;nJ_<98 zx;~Md^G!x2U`KD=2)`)rm!@st=+(Fhf?OsI`=4E^6z|@XEX|&e=JKkTFO>k6DktOY zd_@+P-u~;_o@9gmFFg8DTFtaNYu9jKeGCUYHa1Z5Ped+DDu^Mv ze2SK}e3IMwbyLOk%cHW&MJu(|U$?T1VQeV2QlX`~9W3^?xhYtp#hGEvFZM!+g{f=1 zNXuyVW^T)en6llCXLnJ^GW*YDcgPsiKQ`dX9|d9$TaiZ3vwLsL0bFgs7Hs_SWrh*G za(p(1*Gq=o5Jw=r1#8{#z5#w>tl(jO+Shw4KwW8N1h{!fq_Za>W#Ssw(u>*EIz0IN z`njjTjBHv3xc5?S5QzDCkg%rGgaB@HPrm}VN`uYeL!|Gq68QX=DJ;*G%$l4~#y>iN z&BQQQP$fGGd1WWaF!Q^H)Yxo|AGKD8J8n)e!G=xc#2cy%6GjWuU;pUd$V}lbSW2s= zjbEob)IOxgC7rw}D3+mJ@Rs)Rv>z=Xu+ZlTL>yW^x0?}NG)^prY#~ynTM}q6IAp&%G@EZzC4OguORt*_!l9v#{ zTYfFrxXgc*1=hd;B7XSSWdX23*1yxWfQaj~fMA0NJVgr2aBfDR)PDe=hYRT6Oz2;_ z0SZWP2UT@gP47N_SBdX1A1H0fdA3VGdlpvh#C2G+9;;j!fUMDOhC-LzXeXbi!Lwc) z2Mge!cX7~T1({DCDAVuG{UCzp_)@^nsEfk%Hzi&;_#qr1YQANeuqm`>9XR2XdmO^< zzOni+g%##K%gL_e(t)5TRu=L5&o5|AUFwc{im|5qOqkOHPceF5)iokrNDP=Jkhce4 zK#K3fWF!Xl3jiVPIYF9>i9Aq^0m2GNptm3eDbFQfBjy;V_QWzya}GSAZZFXEoE$O3 z<>%qJsFlGA7O$?4m$WSN$OapcRzEJYGc2gM6wQ*eG%+93ICsUD>r$JYe8K8k^6HOE z*o)Yx@dIe+u9lT0$(Q$3pVaVwaw$s0-9Zco$p><*emSNT7go#f>krd~^BDB_mk)y* zV=|R+aNBO+$eq!(twlA(0`A`e(Dv@`$=v#KJA^aWjD z77T|2bv%dwulr_!K3}N>NiE>k^9EGp-vd5Yr9hTs8$)_`3*dUQa=@A=z(yJTwKXoo zO~4>olUTbSvy=yT1$mW_prDCW*VPj0Wp_8HEEd7;p9g(Z@**p_9t-SqnZW-ysA65#qm_2>?yr0;qUK~x16+fa1a8) zubZm2*}Z$bRT535iK#a`x+PpY8n52X-_-O-i6gkaW76GaYk9fD+v1S5HDz{|zImr( zE1T?nSwmH~*3^W#RvZvGJhSF)WMy~B?25uq>|R=CN9+fz*7cIWLp}L9ZkFNw)k*y$ z#MT{A+r=WDb3^T*wvNs^p{cK=yoEaW^kyl3>5@_RkrI>CPUom|j(q(1QVbre1G52} z&a!nlR!OiiHZe1NBWc2TV!B45n)fYUtR|VcXVuPJ)krUI+Hn>7zFB>}F5ZfF8u1H^ zvh2x-*Pq^t>@-1oy(Ulzd;59ar#BpZK0j6$G-=h>mZzB}IG~CCu~Bh$+5CKDchvFd z!)YJtm=LoY6 zTJ_;v;DuPIx^b@aNLTK_eCQE@lAfnnEE^1o<>!;P%pcqh^bHP1yUu;th{04;8JpqFTa3Odl3_x>A)NPTMHD=8;#H=@>na$5($8 zU<0u3XU;~IWycD~IF^!P?;kxe*%s|rwER|nBB(&{AT}L*_q3qX&nN!eAsbJrd{|d~ ztLM`vJ)k%A-}FAU4)2sZbZ1G&EAeumYwVgI(Ok>q3)5lUnf0iia$W7L&HR}8K@b?T zHH83~>~#>t)*cf{KQTW|a_#A44}Fz{u%HSl=Jz>5=M}aIjL$Y|%41_~{0~dI(`KZrGMs*>^!+l zDnoxcGo}0^|J_g@lJ?c5x?7kQSl8FxuU6!?Z@zTgsdMRi|MXW^UKqHWrF8Dwe-dWEX=ZtnsqnMC4i3Nz_eFs@|8!ROaPSVpm;FI;1N(C#7({%_aInx- z($ZSZX((3rT6d!ay?CSFI76@mA_3S$CA2l`veVD z=ttJZ#a(GjWmdD0R&WYq1^+ zy5UZg0_yMA=N!2{AIQ4BV>I+btCZH3)r3adGZ%a?`2N`Y6#)(pok7aW_S9tjd+f(+ zT688Ru-2xYw10%hgT2}WgM~AI*Zdq;melDu^aPUl8e@N0y(PO>9PET$(L|dGA-eqn zr>gKVf&*I__ky`NfOs=H?}SLsJgHV*3vdm98(g@R2}PExhuh-SIv)U z*Z1>znPQnLoO^zJ$(UMoC(2b#FKb=n;PuMR;gK0zUb^wS2Q^#E56!gpY4)vymfs5^ zmsU3r+(y3lwDh+2R~3$o$V_+S9GSl1l=kFleC0eSE!!6MBa@38fkgE)TK1VfhTm92XnOLZO&1Zg%Rc{Z+^p6&C>rGtF zpSU1o^=Kmm1XZ`zxyQeresjIfbq`4A zKH^ig*_S}U$6gn0paLa{aQn0Y2URBCkln@RQu*?f4-QQ>ls`c`07F;jpp3~Oq063^ zWVj`bf0;M_5h&u;#hzmWDN1Ib=cbl&J3k(17|`HL#>S>A+? z-t#-3%%B@htl-2CwMhcyc&k}|!zmOzwxWkyo(LrS;!Z1(9Z!%4Zf`bI^5Jj%xPH1( zm~dlsyb-nW;}No^T$fbVZ;gmK{?hEQ(yzPAjLNCP!^hkETI6^7i1bv-r@uLNJWFK zSFsfLqdbUzuWvCa24-SUUFridmEDt~Kfi_Ud5?JrLQLeId}z#WiHESx1{cq|a*I7R z-R#C!h>G?>cIqhr?UI;rRj^r;L*G1mW8D@0Wg$VoCpx5}*0?;2aQ+>VOy-5p)-kK3 zfT?|}g-F%fqI(K}c4P$s;Qoz!i>BvZP#UHR5JZYz?9`2_S1xd2{(S)^_vZpMt<5nCQ@4 zj!btR_Ix9!%XEVJOzF(>i$-O>SC69=qYm1ST7*sdd(&StwaVKmT!aCr;G8FrPcMHJniI?Zhnf|H&|3DV^;3XF;}$B@7K z-7#L8+YY}}r`Rak&#@8X8CBKgqtS(2c$SoDtoz87de6dH#(w{Xv5m6k6uA>gpI(>% z&W<|8oKPP%wC{G4cKbo7uV5Vuw+KXYMvLM{uUnsuj8iAH*cY8j_Ty(kRv(0P2 zImPmTn_Fah?>U!~^7a6>hpRQ^=qhWI?+Rk0b@uWT`x}Y5cFNE$0F2jM?g(GCtCPPi zgUjY&TkZuEyec5NCD+6{xjojB{5?B+R|lK>*)a^YIWhq)F*yHt#k1etRj2)+qKr*Q zNts&dYlD3oI5F0fMz{P}*?dsvaT;ZB!wH(wf2E9zKY+tDn?p_~R!!RzAshzzw26iDaGa>VS^_)OG&_9>e(9OjQ zja+UAd{3`(CU;gJo-12YHEaCg2rcpMm7_kgg$-rwC$sCe<(SQ1-Wz&aCVOdqo-3v^ z)1mgc21TELhppu@i-OP4gW;$ItuGcx2zU@c5#wmow+Wu??fy0 z!ciUvAwmO2rJUGH9KF)TvwFw-_JH&$I2~v}ggbNcTnA4an_HVn00W6FC-ml<7(qdv zhLUZq9__Dx{b+t-9VIF6*jNx=AGryueG@rrPaELET?Zbm-E0XSGwFf!%3Bw)$FcMr zVD9@l@lc?F40O4j&Ohe)MQee6+NHUP9jb-3U z04Mc#Y|mAUF>M=L@ob-@u{J;vR+zPvpM-(=dN)6S#wHY_NV*UyQG7CW8VJ#47#tae zOK34^tYKm|KIBuG*6*HalEFna>%7LLl&g>92EQ&%EY-{WytP?)WkPO`2ev-Zwq5+q+MuVSDu(MR@z=!4 zlCR~H+;mdy5ElW`1KsSNlE5vn!qBjse+hu0cM-Y_P~-I zQ)nej-fVx=+-4Qsa#IpY>D)s5xzG`f9SfmJbSyt6dd8JN$I4w33493Xb6t_r0jd@>uGvf{f5$p&fa)>s71e+1mxJRyAuqH5T-wic{7bD2>{TMbC4UNh`HSuFczh471v@adoCC6w4 zDly$*NQ8+r_)t^P^M$mQ1+*{K^^BB~FNPypd)MAdB&{2jLDEHDrXnzLn+69prSF@8 z`nhLf2S&}QeE{iVK44=^faTfS-s2z7^TG>!bm)P!9|Dw|s8r}0)cB?)B4Ds7`0~p~ zRT~2CPbQe@K1&li`NyAMnPtGvrs<8oTA~}V(8GDot?#XAP_Hvt-~F6SSDmKRe!FMC zL~!QA=&}qePHmRE%!-BhHHU;*XQXcv5caa7B1!S7<_e(GgAWNU_ySyv_(H_rZr|>8 z$RbMqj007MF)_RD9_=ogF5mVL@v2#VA}f-Lgx>gc>K+^fw{y;`&kM!Yz=ELu$ciqS zADx)qu(iFbi_V3&u#N6Xy4@aNE8!!&VEmIxF66*R8$uOyPTVU%u!H1{sRnKieA`bx z5&~b4-lrw3^aqfkx2RE_bQ??y!T$VVRfG=1f{K(O=MrM~!}m55t2jj=OWck4T@W~z zl*yepBGkq&JHpUA{5dS@B)_>W`QnjnYv<4iVfynX3i-=bqN2W(?&K$5DD5G9Iq5%r zTA0NZlh8;@+a$Y%h~rjUfHvaMU3vURF45-65dyGc!xbWwciW*2#(Y^i+hPX@f&(X> zP+Ty1AFCbc{TltUR&#^!UCx!lb~oh|Irht6cVr0A>@vtOz$1u{3qtttT!|iq08Eaw zcgmejh}5Tg!(Rlu5y&^s58q?wj+4{ccVP@HXRhUw1>$P^N&Y$& zob&` zHZ}JNWqh0<$YHgwX!Q|(bb9UFLGPmN0M`J6&)mN(9f|NV>vu{7vG&5h6Uxig~AX`+TX+Fj88!swMKkR5eKE4NAn5od8@H778# zHgQdFN52(FEFnyIAsL53gaF{5ee>=SR6u?!-VTb0*%c9T!#ca^2ldrHZdv+d_paV! zdDm2RQk##nQBzQfcoo?DNi2eOS(>H!BA~NT(?4lvEj=Nw)*#zk8lCuA4z2hrQsj0- zk840Xb<}Tc*WhxrTuvRich#RjJ7R0CLm#ZR+Ki{18t!~6#Yoax2A_*~#+l~5wkOu! z4SF>E_2CO1?A`FEj&(KuPj;1>Ud$w(@%r-e2#++&d^xZe9( z=)m4Y02K9s4(aoS}Fvsgp`l5 z_Q;ey&F_%mNjK!g$rXCmLj>j)9{Bc(Kzod)3-`y1if&k=13qJmqPf}+X}nXeKvi!2J#Xz@rATdW{U%E`x0kzgwsi9E-oxov zM0v)H4~>w4S&30^HH1PjFo>Rq8wCB0 zk%__UXVJ-Fdn__H@Rc0E{-F)>u^tN+xz{Gy)fC6u~yNdN698 zb&>#iBrv}x$>7v><$zMNY)%}E^@p1?@5Z~(c8hcGS32FEEBlUgmgc!Wcz9GEag_CG z|9QoVtDk5$Ic^%IZT362Y-d?MH;NW}Q&z*ll1&l9pFKaobN!yM71=1l%)HHqxw$@j z>d<*qwQWRiwVwzPir;=vLxGRsA=oLfYo*RSYWU4R@y+RAR!BuhE&PB;;S z!L3_Ig((bzu68`51HJ?t4|G_{fY%qfk-?%aA;a^dxBzF>9X+NTiMF}5GqG_AH1kFl zcof6b4X|cDz8@ZFezXf^k4t3~4$kw-Nz zd>xV>Cg)unCL}ul2k{{>Y8X*Se_c@0|q;LWZGrjG;AraxOeTu^{&CIVS;pGJ@I*y03dvcv)j;=m4CFrpZ zxJu{mktY{?NQ`%q3?g$y30F@~?9~G>KG8Dsh5)VBftL@k$8|x(fkDIKQOp_16k{sT3GgdQuTT)^T`?=%ei?x%KAnuw2$TV|_Mt|I^`0w1wec_r zG7Yr9@=7}G2|tgIBs9Lqn{O9Jae(p0D`~4ZujOYsE-lcLy^}nd_9b4dW|w-)dbA(5 zFq-ePFnY!lGak9p34Ky3`0N2JhLszcRAVtb4-b zS<;GiqsdkXR;gfL`c)|oR4%A>+0TJS*x`4H;|^CJ!NcsQLU)(=D|mW6^CFq9&w7xv zDNL5m3*PMFGEiCVmxAF7);x%bYltOckhR1WE14fB-z0%?bb0J!e*u`CKQPe`qmOvG zF_F9_$?)`tohGhxM(ux9>xH#cHd$FmYbf z1N`QX_OKq_3fZo;rtRa=FYP@D)ca)8Jooe7Y4S8D*u)t`wOuE#U8#2QHn$-cZXWaX z^ti(%!sze>0X%Boc>x{}ydc0Pb3$AD$Ip|gS8JwzM>y&`^!xqZ8_Ugzoz2FODfFBg zLV644C!^OUJyj;c{AyOkFH&K|<}hBs5B3kCffi0TcPL*biQ>CWXzVhq!Y~#FY7AKe zm_S{ndKU)ZA2NiLDjqGRQY1H>%ex5`nFb9ZNSqXay#Nr$oiTX|OT5;chxrZ*#|oDb zc#@Yf%!r-j)jybH6t+bnyk+`I zV8poZ^)|N!K46f-pM+d|zIDZh;Ja#Y8t}&+FQ>iap|vlcs5g3hnK2R4%I}qjtOCl! zkdCyP{B(Fvs8f_D*V*zG2hPdqfj3NbX6D{$P2_fW&dqD=T*Cq3?JZ>87B6h})Da9a zt*@9M1vzqgM;t$J)!m+d-6{ef2ZmH(Zou`#3{u?D7178>-t50;9S+l-w=g+i-1AE+ z;i%gIHec933r)9S>W^CsUH4P@aLD5#8j^5+Kh-v~Dtd*&fSR8$#TdUJX1t)hds@UE zQS7vydRMO6;7Vw{*E)N@S061G5D%fNd$-ZM3=!9_g@OBRw)zw#mgQ^n;66Z!u&gGz zJuZoO@@DN<85kvtJbxc3i1%IBX@}28uRNV7IKHe|%5#H%o8(rfo_M!&9T#2!JegMx z8I^=ul6QcsX1@%>>-Fz5Qh4g)1qE?2g zAjUde8Qi-t-sy(_9PfGRKM0VvV4S}t>JU!^@4Hc(3A<-ofMH&ANN zz#rf`cH!u!fFyFKtJX8AoDcqzYTF$+8A8CLe9WeO-FiI5^+#vPJIlJ+Z@EM)=3p%! zJU5bBd%o@9>GmQ~9Ba8mjY$cnIb&q54s`s}kY5cVGRE-0QG3dcF`pBnl+&lQ87vbrqMVvjB zZoBwQ#=dHP9hXFh_}1)#Ik)?LOPTsM5q6{dUZ++c|KvAqcK+?wyX16kn_pN3m(`q} z4#l)c$!=}uspL6fS)riK=Kj-MU^5q2x?FrT`IVy9ik|Nxr?k@S9mFu?0I9DW4=;+@ zC`Io+zsgxiK7{0**}DvC*XIy2g6>|lLuVXEPv4uuT{B5|stzbkv~eR(SO033sgEG0 zKRq)~s$SS&jR!A0L>7Lzi*_jrpdfSH=o$vp*?0VkmHHTCpDGLro>{eSp^yx;gEEkG z;%a0BU)@43`Mo0P1<_<;atGwq+MIY@tV1C#5SY#4t;?}qP64Unw!PZkVjJoGHqVq(|Sold13%BG+j;vX$1uZfgZt1Ug1+?@GGTe0W!B3D0t2P36d6xEvI4X~|~ z+Ju6=4q^(8+&H~*EWA2PH8iV$Z*4Zf+_k@A0Xg6J_JtWL zT7}~uPS4LP1D*XI7@D6}b#?)ajlYQ{0xM#78T40~7vnc1Y{6!wrY#H!A%b&BX=SSX zCMZOsl}Iwz;|HnzxX~uOY=c`3@!7}M{E%xnU2OqOH05MRUm918SCo#OKUp}o>W|&RUA!z>aY-Z5urG~$DWJ0%pzp* zBM1F@yCe7e&)8UmU;&CR0QW6GPn)5}%13@ACbM@cT6GXn>5_G(1i4=fjoucrcL$m} z;!t}}9dkobS8n-tFZ?4VFTIz6#yWQ?US!^1SNseU5d8Fb_tUpn!GStKjY zZ|VJf?js0umhB_rBNBz*E>DRt?Sql}`Lb_COh@BT8uA*1sj_zw{Bmz{6B-Pn#C1Aj zI`jsa17^HteDBF;Nj7({jNX6lSd0w)aOM{jEVN*Zn4g~AsR%WqnJhdc!YDy+<@};c z&d75y9!BT!fKh&kr?AB3II4}$4Bh7iYV5>qXqVuGl$jMs1)RWQGRUJ&M~?CW2i^v) zyf^r1GAutln~+Az3*`luDd#4G^VA0tTO@3=!_tfIY!}RasCHR6!S^4ddi@a3(|KdJ zt(*ak%+@5%>T@>d?H%IL)(s_ca0F6rf63%4;^%|A>)Qk$A~a%M^=03aAeq6@4*Cbz z^vQj4T)4FKP~dL=y?O-Zp3p`Nyc~>eb5)t@iaBLC4!aqAzyfAUa2vB$G49ZWmUkAnC5@5{v!vsg#`Zp(|jjlr2 z$*W7W;iDjY#tFU=rjR>j+>-fG3E>caQfKq@Qj0U2q=(FhGy-b2{41zEcA2VOvo)T8BmB1Qr^0e7r`{4kuOwmL;%=0?L0foJk(bkQCP4LVa_=RNuc}K&?Mc6XsVvH7=r;X73 z2J?1Gsp(z_aYG?g=zRo3ibL?U9rZ8J?9-2!UR*6W5C+wA9_>;C2qPDA$|HJQPSskcB6gc;q>q18%6_+ABdH78=Z+|bfUSTNy51gb=TZa9eM zB^Egzd>g_EbhhehMiTOnq$@LZ78!8Z$u)nf1f_%pfrP)lC(f8ZuYz{yrnfOFd09gA zCZe+;aspQ}8G@sH-sc1u9O)d-$#weBRs4M}Rh#?xu(s%{_C2k^CCdxHZWwuO%~eh> z_~-EAR5;)CY_}c_M&G1IHM`k~ujdbUu*io_PCE7eH%Rvm-*mTdjE31DpY&-oD@JiA z3VtJwc_NgwKHd4}!n^qK-0|ab%}~g`yA2osW#=x9JnmivG(P>6E&}#Mde_ggGBkD+ zlO^k$9U8uZw~4syD`{u{6J-Sr9bnqH0i!0V?JgN{k5e$Q!wH?-cu@vy z(S&IZ{8S==!tZe$uEJI&6lG45YlFqWF8Kc-qx-N;O8pHc{$MdShl0 zhX;T1U)g*^?=QIF9D9|z#6u|@@*-@Z*O%4V(p2m%kKU_G17(!+v$V;*opBijgfp5D2es0jEm;#K$dFYm`&)$N<@GF#Q^9nY z=C`X@>9Z zl>)|Stn=nd4g&1ivn#6O1ePJVcjDT*vrcH=?6(lO9e!mz#S#?@{z9clu8m@yDBPkUIRf{zI30BJ z2pyzR43=m&UNdv6r|MSi`#!|Uh}a?u#wE_RtGQ!KPhIMIW2!FNlH~IyZmq&AiA|~| zn)=I~R~XB%etkKt@_Y&3vah7=cQ%Tdf?`YRjLvG`wVF!9-K*=%t&@$NDq>cLJulr6 z8u!jole$*(9Y#}V|OV_h*ZcMuEuDv95ZTwKEaLHtqt3DLrp n9DWVC{B>BP8l0jL8`ZjmcXlzeu0N^&Ky>6RF8M4j>J9%3;jZ}4 literal 0 HcmV?d00001 diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 36cbbac8..5f1526fd 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -1170,6 +1170,21 @@ export class CallViewModel extends ViewModel { }), this.scope.state(), ); + /** + * Emits an event every time a new screenshare is started in + * the call. + */ + public readonly newScreenShare$ = this.screenShares$.pipe( + map((v) => v.length), + scan( + (acc, newValue) => ({ + value: newValue, + playSounds: newValue > acc.value, + }), + { value: 0, playSounds: false }, + ), + filter((v) => v.playSounds), + ); public constructor( // A call is permanently tied to a single Matrix room and LiveKit room From abf2ecd5217a584d5f32de12f492e7a742f20758 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 19 Dec 2024 15:54:28 +0000 Subject: [PATCH 66/72] Refactor reactions / hand raised to use rxjs and start ordering tiles based on hand raised. (#2885) * Add support for using CallViewModel for reactions sounds. * Drop setting * Convert reaction sounds to call view model / rxjs * Use call view model for hand raised reactions * Support raising reactions for matrix rtc members. * Tie up last bits of useReactions * linting * Update calleventaudiorenderer * Update reaction audio renderer * more test bits * All the test bits and pieces * More refactors * Refactor reactions into a sender and receiver. * Fixup reaction toggle button * Adapt reactions test * Tests all pass. * lint * fix a couple of bugs * remove unused helper file * lint * finnish notation * Add tests for useReactionsReader * remove mistaken vitest file * fix * filter * invert * fixup tests with fake timers * Port useReactionsReader hook to ReactionsReader class. * lint * exclude some files from coverage * Add screen share sound effect. * cancel sub on destroy * tidy tidy --- src/button/ReactionToggleButton.test.tsx | 168 +++--- src/button/ReactionToggleButton.tsx | 28 +- .../ReactionToggleButton.test.tsx.snap | 20 +- src/reactions/ReactionsReader.test.tsx | 515 ++++++++++++++++++ src/reactions/ReactionsReader.ts | 339 ++++++++++++ src/reactions/index.ts | 20 + src/reactions/useReactionsSender.tsx | 174 ++++++ src/room/CallEventAudioRenderer.test.tsx | 180 +++--- src/room/CallEventAudioRenderer.tsx | 21 +- src/room/GroupCallView.test.tsx | 7 + src/room/GroupCallView.tsx | 2 +- src/room/InCallView.tsx | 28 +- src/room/ReactionAudioRenderer.test.tsx | 100 ++-- src/room/ReactionAudioRenderer.tsx | 45 +- src/room/ReactionsOverlay.test.tsx | 123 ++--- src/room/ReactionsOverlay.tsx | 28 +- src/state/CallViewModel.test.ts | 72 ++- src/state/CallViewModel.ts | 113 +++- src/state/MediaViewModel.ts | 27 +- src/tile/GridTile.test.tsx | 12 +- src/tile/GridTile.tsx | 18 +- src/useReactions.test.tsx | 173 ------ src/useReactions.tsx | 405 -------------- src/utils/test-fixtures.ts | 24 + src/utils/test-viewmodel.ts | 150 +++++ src/utils/test.ts | 6 + src/utils/testReactions.tsx | 214 -------- vitest.config.js | 7 +- 28 files changed, 1835 insertions(+), 1184 deletions(-) create mode 100644 src/reactions/ReactionsReader.test.tsx create mode 100644 src/reactions/ReactionsReader.ts create mode 100644 src/reactions/useReactionsSender.tsx delete mode 100644 src/useReactions.test.tsx delete mode 100644 src/useReactions.tsx create mode 100644 src/utils/test-fixtures.ts create mode 100644 src/utils/test-viewmodel.ts delete mode 100644 src/utils/testReactions.tsx diff --git a/src/button/ReactionToggleButton.test.tsx b/src/button/ReactionToggleButton.test.tsx index 72437825..da3b6fc6 100644 --- a/src/button/ReactionToggleButton.test.tsx +++ b/src/button/ReactionToggleButton.test.tsx @@ -5,47 +5,47 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { render } from "@testing-library/react"; +import { act, render } from "@testing-library/react"; import { expect, test } from "vitest"; import { TooltipProvider } from "@vector-im/compound-web"; import { userEvent } from "@testing-library/user-event"; import { type ReactNode } from "react"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; -import { - MockRoom, - MockRTCSession, - TestReactionsWrapper, -} from "../utils/testReactions"; import { ReactionToggleButton } from "./ReactionToggleButton"; import { ElementCallReactionEventType } from "../reactions"; +import { type CallViewModel } from "../state/CallViewModel"; +import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel"; +import { alice, local, localRtcMember } from "../utils/test-fixtures"; +import { type MockRTCSession } from "../utils/test"; +import { ReactionsSenderProvider } from "../reactions/useReactionsSender"; -const memberUserIdAlice = "@alice:example.org"; -const memberEventAlice = "$membership-alice:example.org"; - -const membership: Record = { - [memberEventAlice]: memberUserIdAlice, -}; +const localIdent = `${localRtcMember.sender}:${localRtcMember.deviceId}`; function TestComponent({ rtcSession, + vm, }: { rtcSession: MockRTCSession; + vm: CallViewModel; }): ReactNode { return ( - - - + + + ); } test("Can open menu", async () => { const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); + const { vm, rtcSession } = getBasicCallViewModelEnvironment([alice]); const { getByLabelText, container } = render( - , + , ); await user.click(getByLabelText("common.reactions")); expect(container).toMatchSnapshot(); @@ -53,102 +53,120 @@ test("Can open menu", async () => { test("Can raise hand", async () => { const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); + const { vm, rtcSession, handRaisedSubject$ } = + getBasicCallViewModelEnvironment([local, alice]); const { getByLabelText, container } = render( - , + , ); await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.raise_hand")); - expect(room.testSentEvents).toEqual([ - [ - undefined, - "m.reaction", - { - "m.relates_to": { - event_id: memberEventAlice, - key: "🖐️", - rel_type: "m.annotation", - }, + expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith( + rtcSession.room.roomId, + "m.reaction", + { + "m.relates_to": { + event_id: localRtcMember.eventId, + key: "🖐️", + rel_type: "m.annotation", }, - ], - ]); + }, + ); + act(() => { + // Mock receiving a reaction. + handRaisedSubject$.next({ + [localIdent]: { + time: new Date(), + reactionEventId: "", + membershipEventId: localRtcMember.eventId!, + }, + }); + }); expect(container).toMatchSnapshot(); }); test("Can lower hand", async () => { + const reactionEventId = "$my-reaction-event:example.org"; const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); + const { vm, rtcSession, handRaisedSubject$ } = + getBasicCallViewModelEnvironment([local, alice]); const { getByLabelText, container } = render( - , + , ); - const reactionEvent = room.testSendHandRaise(memberEventAlice, membership); + await user.click(getByLabelText("common.reactions")); + await user.click(getByLabelText("action.raise_hand")); + act(() => { + handRaisedSubject$.next({ + [localIdent]: { + time: new Date(), + reactionEventId, + membershipEventId: localRtcMember.eventId!, + }, + }); + }); await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.lower_hand")); - expect(room.testRedactedEvents).toEqual([[undefined, reactionEvent]]); + expect(rtcSession.room.client.redactEvent).toHaveBeenCalledWith( + rtcSession.room.roomId, + reactionEventId, + ); + act(() => { + // Mock receiving a redacted reaction. + handRaisedSubject$.next({}); + }); expect(container).toMatchSnapshot(); }); test("Can react with emoji", async () => { const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); + const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]); const { getByLabelText, getByText } = render( - , + , ); await user.click(getByLabelText("common.reactions")); await user.click(getByText("🐶")); - expect(room.testSentEvents).toEqual([ - [ - undefined, - ElementCallReactionEventType, - { - "m.relates_to": { - event_id: memberEventAlice, - rel_type: "m.reference", - }, - name: "dog", - emoji: "🐶", + expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith( + rtcSession.room.roomId, + ElementCallReactionEventType, + { + "m.relates_to": { + event_id: localRtcMember.eventId, + rel_type: "m.reference", }, - ], - ]); + name: "dog", + emoji: "🐶", + }, + ); }); test("Can fully expand emoji picker", async () => { const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { getByText, container, getByLabelText } = render( - , + const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]); + const { getByLabelText, container, getByText } = render( + , ); await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.show_more")); expect(container).toMatchSnapshot(); await user.click(getByText("🦗")); - - expect(room.testSentEvents).toEqual([ - [ - undefined, - ElementCallReactionEventType, - { - "m.relates_to": { - event_id: memberEventAlice, - rel_type: "m.reference", - }, - name: "crickets", - emoji: "🦗", + expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith( + rtcSession.room.roomId, + ElementCallReactionEventType, + { + "m.relates_to": { + event_id: localRtcMember.eventId, + rel_type: "m.reference", }, - ], - ]); + name: "crickets", + emoji: "🦗", + }, + ); }); test("Can close reaction dialog", async () => { const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); + const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]); const { getByLabelText, container } = render( - , + , ); await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.show_more")); diff --git a/src/button/ReactionToggleButton.tsx b/src/button/ReactionToggleButton.tsx index 7f231d30..e01d06e8 100644 --- a/src/button/ReactionToggleButton.tsx +++ b/src/button/ReactionToggleButton.tsx @@ -24,8 +24,10 @@ import { import { useTranslation } from "react-i18next"; import { logger } from "matrix-js-sdk/src/logger"; import classNames from "classnames"; +import { useObservableState } from "observable-hooks"; +import { map } from "rxjs"; -import { useReactions } from "../useReactions"; +import { useReactionsSender } from "../reactions/useReactionsSender"; import styles from "./ReactionToggleButton.module.css"; import { type ReactionOption, @@ -33,6 +35,7 @@ import { ReactionsRowSize, } from "../reactions"; import { Modal } from "../Modal"; +import { type CallViewModel } from "../state/CallViewModel"; interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> { raised: boolean; @@ -162,22 +165,27 @@ export function ReactionPopupMenu({ } interface ReactionToggleButtonProps extends ComponentPropsWithoutRef<"button"> { - userId: string; + identifier: string; + vm: CallViewModel; } export function ReactionToggleButton({ - userId, + identifier, + vm, ...props }: ReactionToggleButtonProps): ReactNode { const { t } = useTranslation(); - const { raisedHands, toggleRaisedHand, sendReaction, reactions } = - useReactions(); + const { toggleRaisedHand, sendReaction } = useReactionsSender(); const [busy, setBusy] = useState(false); const [showReactionsMenu, setShowReactionsMenu] = useState(false); const [errorText, setErrorText] = useState(); - const isHandRaised = !!raisedHands[userId]; - const canReact = !reactions[userId]; + const isHandRaised = useObservableState( + vm.handsRaised$.pipe(map((v) => !!v[identifier])), + ); + const canReact = useObservableState( + vm.reactions$.pipe(map((v) => !v[identifier])), + ); useEffect(() => { // Clear whenever the reactions menu state changes. @@ -223,7 +231,7 @@ export function ReactionToggleButton({ setShowReactionsMenu((show) => !show)} - raised={isHandRaised} + raised={!!isHandRaised} open={showReactionsMenu} {...props} /> @@ -237,8 +245,8 @@ export function ReactionToggleButton({ > void sendRelation(reaction)} toggleRaisedHand={wrappedToggleRaisedHand} /> diff --git a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap index dd4227e1..4937bae3 100644 --- a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap +++ b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap @@ -9,7 +9,7 @@ exports[`Can close reaction dialog 1`] = ` aria-disabled="false" aria-expanded="true" aria-haspopup="true" - aria-labelledby=":r9l:" + aria-labelledby=":rav:" class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" data-kind="primary" data-size="lg" @@ -43,7 +43,7 @@ exports[`Can fully expand emoji picker 1`] = ` aria-disabled="false" aria-expanded="true" aria-haspopup="true" - aria-labelledby=":r6c:" + aria-labelledby=":r7m:" class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" data-kind="primary" data-size="lg" @@ -75,8 +75,8 @@ exports[`Can lower hand 1`] = ` aria-expanded="false" aria-haspopup="true" aria-labelledby=":r36:" - class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59" - data-kind="primary" + class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" + data-kind="secondary" data-size="lg" role="button" tabindex="0" @@ -90,7 +90,9 @@ exports[`Can lower hand 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -138,8 +140,8 @@ exports[`Can raise hand 1`] = ` aria-expanded="false" aria-haspopup="true" aria-labelledby=":r1j:" - class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" - data-kind="secondary" + class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59" + data-kind="primary" data-size="lg" role="button" tabindex="0" @@ -153,9 +155,7 @@ exports[`Can raise hand 1`] = ` xmlns="http://www.w3.org/2000/svg" > diff --git a/src/reactions/ReactionsReader.test.tsx b/src/reactions/ReactionsReader.test.tsx new file mode 100644 index 00000000..b66550f7 --- /dev/null +++ b/src/reactions/ReactionsReader.test.tsx @@ -0,0 +1,515 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { renderHook } from "@testing-library/react"; +import { afterEach, test, vitest } from "vitest"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; +import { + RoomEvent as MatrixRoomEvent, + MatrixEvent, + type IRoomTimelineData, + EventType, + MatrixEventEvent, +} from "matrix-js-sdk/src/matrix"; + +import { ReactionsReader, REACTION_ACTIVE_TIME_MS } from "./ReactionsReader"; +import { + alice, + aliceRtcMember, + local, + localRtcMember, +} from "../utils/test-fixtures"; +import { getBasicRTCSession } from "../utils/test-viewmodel"; +import { withTestScheduler } from "../utils/test"; +import { ElementCallReactionEventType, ReactionSet } from "."; + +afterEach(() => { + vitest.useRealTimers(); +}); + +test("handles a hand raised reaction", () => { + const { rtcSession } = getBasicRTCSession([local, alice]); + const reactionEventId = "$my_event_id:example.org"; + const localTimestamp = new Date(); + withTestScheduler(({ schedule, expectObservable }) => { + renderHook(() => { + const { raisedHands$ } = new ReactionsReader( + rtcSession as unknown as MatrixRTCSession, + ); + schedule("ab", { + a: () => {}, + b: () => { + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: EventType.Reaction, + origin_server_ts: localTimestamp.getTime(), + content: { + "m.relates_to": { + event_id: localRtcMember.eventId, + key: "🖐️", + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + }, + }); + expectObservable(raisedHands$).toBe("ab", { + a: {}, + b: { + [`${localRtcMember.sender}:${localRtcMember.deviceId}`]: { + reactionEventId, + membershipEventId: localRtcMember.eventId, + time: localTimestamp, + }, + }, + }); + }); + }); +}); + +test("handles a redaction", () => { + const { rtcSession } = getBasicRTCSession([local, alice]); + const reactionEventId = "$my_event_id:example.org"; + const localTimestamp = new Date(); + withTestScheduler(({ schedule, expectObservable }) => { + renderHook(() => { + const { raisedHands$ } = new ReactionsReader( + rtcSession as unknown as MatrixRTCSession, + ); + schedule("abc", { + a: () => {}, + b: () => { + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: EventType.Reaction, + origin_server_ts: localTimestamp.getTime(), + content: { + "m.relates_to": { + event_id: localRtcMember.eventId, + key: "🖐️", + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + }, + c: () => { + rtcSession.room.emit( + MatrixRoomEvent.Redaction, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: EventType.RoomRedaction, + redacts: reactionEventId, + }), + rtcSession.room, + undefined, + ); + }, + }); + expectObservable(raisedHands$).toBe("abc", { + a: {}, + b: { + [`${localRtcMember.sender}:${localRtcMember.deviceId}`]: { + reactionEventId, + membershipEventId: localRtcMember.eventId, + time: localTimestamp, + }, + }, + c: {}, + }); + }); + }); +}); + +test("handles waiting for event decryption", () => { + const { rtcSession } = getBasicRTCSession([local, alice]); + const reactionEventId = "$my_event_id:example.org"; + const localTimestamp = new Date(); + withTestScheduler(({ schedule, expectObservable }) => { + renderHook(() => { + const { raisedHands$ } = new ReactionsReader( + rtcSession as unknown as MatrixRTCSession, + ); + schedule("abc", { + a: () => {}, + b: () => { + const encryptedEvent = new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: EventType.Reaction, + origin_server_ts: localTimestamp.getTime(), + content: { + "m.relates_to": { + event_id: localRtcMember.eventId, + key: "🖐️", + }, + }, + }); + // Should ignore encrypted events that are still encrypting + encryptedEvent["decryptionPromise"] = Promise.resolve(); + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + encryptedEvent, + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + }, + c: () => { + rtcSession.room.client.emit( + MatrixEventEvent.Decrypted, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: EventType.Reaction, + origin_server_ts: localTimestamp.getTime(), + content: { + "m.relates_to": { + event_id: localRtcMember.eventId, + key: "🖐️", + }, + }, + }), + ); + }, + }); + expectObservable(raisedHands$).toBe("a-c", { + a: {}, + c: { + [`${localRtcMember.sender}:${localRtcMember.deviceId}`]: { + reactionEventId, + membershipEventId: localRtcMember.eventId, + time: localTimestamp, + }, + }, + }); + }); + }); +}); + +test("hands rejecting events without a proper membership", () => { + const { rtcSession } = getBasicRTCSession([local, alice]); + const reactionEventId = "$my_event_id:example.org"; + const localTimestamp = new Date(); + withTestScheduler(({ schedule, expectObservable }) => { + renderHook(() => { + const { raisedHands$ } = new ReactionsReader( + rtcSession as unknown as MatrixRTCSession, + ); + schedule("ab", { + a: () => {}, + b: () => { + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: EventType.Reaction, + origin_server_ts: localTimestamp.getTime(), + content: { + "m.relates_to": { + event_id: "$not-this-one:example.org", + key: "🖐️", + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + }, + }); + expectObservable(raisedHands$).toBe("a-", { + a: {}, + }); + }); + }); +}); + +test("handles a reaction", () => { + const { rtcSession } = getBasicRTCSession([local, alice]); + const reactionEventId = "$my_event_id:example.org"; + const reaction = ReactionSet[1]; + + vitest.useFakeTimers(); + vitest.setSystemTime(0); + + withTestScheduler(({ schedule, time, expectObservable }) => { + renderHook(() => { + const { reactions$ } = new ReactionsReader( + rtcSession as unknown as MatrixRTCSession, + ); + schedule(`abc`, { + a: () => {}, + b: () => { + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: ElementCallReactionEventType, + content: { + emoji: reaction.emoji, + name: reaction.name, + "m.relates_to": { + event_id: localRtcMember.eventId, + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + }, + c: () => { + vitest.advanceTimersByTime(REACTION_ACTIVE_TIME_MS); + }, + }); + expectObservable(reactions$).toBe( + `ab ${REACTION_ACTIVE_TIME_MS - 1}ms c`, + { + a: {}, + b: { + [`${localRtcMember.sender}:${localRtcMember.deviceId}`]: { + reactionOption: reaction, + expireAfter: new Date(REACTION_ACTIVE_TIME_MS), + }, + }, + // Expect reaction to expire. + c: {}, + }, + ); + }); + }); +}); + +test("ignores bad reaction events", () => { + const { rtcSession } = getBasicRTCSession([local, alice]); + const reactionEventId = "$my_event_id:example.org"; + const reaction = ReactionSet[1]; + + vitest.setSystemTime(0); + + withTestScheduler(({ schedule, expectObservable }) => { + renderHook(() => { + const { reactions$ } = new ReactionsReader( + rtcSession as unknown as MatrixRTCSession, + ); + schedule("ab", { + a: () => {}, + b: () => { + // Missing content + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: ElementCallReactionEventType, + content: {}, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + // Wrong relates event + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: ElementCallReactionEventType, + content: { + emoji: reaction.emoji, + name: reaction.name, + "m.relates_to": { + event_id: "wrong-event", + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + // Wrong rtc member event + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: aliceRtcMember.sender, + type: ElementCallReactionEventType, + content: { + emoji: reaction.emoji, + name: reaction.name, + "m.relates_to": { + event_id: localRtcMember.eventId, + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + // No emoji + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: ElementCallReactionEventType, + content: { + name: reaction.name, + "m.relates_to": { + event_id: localRtcMember.eventId, + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + // Invalid emoji + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: ElementCallReactionEventType, + content: { + emoji: " ", + name: reaction.name, + "m.relates_to": { + event_id: localRtcMember.eventId, + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + }, + }); + expectObservable(reactions$).toBe("a-", { + a: {}, + }); + }); + }); +}); + +test("that reactions cannot be spammed", () => { + const { rtcSession } = getBasicRTCSession([local, alice]); + const reactionEventId = "$my_event_id:example.org"; + const reactionA = ReactionSet[1]; + const reactionB = ReactionSet[2]; + + vitest.useFakeTimers(); + vitest.setSystemTime(0); + + withTestScheduler(({ schedule, expectObservable }) => { + renderHook(() => { + const { reactions$ } = new ReactionsReader( + rtcSession as unknown as MatrixRTCSession, + ); + schedule("abcd", { + a: () => {}, + b: () => { + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: ElementCallReactionEventType, + content: { + emoji: reactionA.emoji, + name: reactionA.name, + "m.relates_to": { + event_id: localRtcMember.eventId, + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + }, + c: () => { + rtcSession.room.emit( + MatrixRoomEvent.Timeline, + new MatrixEvent({ + room_id: rtcSession.room.roomId, + event_id: reactionEventId, + sender: localRtcMember.sender, + type: ElementCallReactionEventType, + content: { + emoji: reactionB.emoji, + name: reactionB.name, + "m.relates_to": { + event_id: localRtcMember.eventId, + }, + }, + }), + rtcSession.room, + undefined, + false, + {} as IRoomTimelineData, + ); + }, + d: () => { + vitest.advanceTimersByTime(REACTION_ACTIVE_TIME_MS); + }, + }); + expectObservable(reactions$).toBe( + `ab- ${REACTION_ACTIVE_TIME_MS - 2}ms d`, + { + a: {}, + b: { + [`${localRtcMember.sender}:${localRtcMember.deviceId}`]: { + reactionOption: reactionA, + expireAfter: new Date(REACTION_ACTIVE_TIME_MS), + }, + }, + d: {}, + }, + ); + }); + }); +}); diff --git a/src/reactions/ReactionsReader.ts b/src/reactions/ReactionsReader.ts new file mode 100644 index 00000000..c0c1009d --- /dev/null +++ b/src/reactions/ReactionsReader.ts @@ -0,0 +1,339 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { + type CallMembership, + MatrixRTCSessionEvent, + type MatrixRTCSession, +} from "matrix-js-sdk/src/matrixrtc"; +import { logger } from "matrix-js-sdk/src/logger"; +import { type MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix"; +import { type ReactionEventContent } from "matrix-js-sdk/src/types"; +import { + RelationType, + EventType, + RoomEvent as MatrixRoomEvent, +} from "matrix-js-sdk/src/matrix"; +import { BehaviorSubject, delay, type Subscription } from "rxjs"; + +import { + ElementCallReactionEventType, + type ECallReactionEventContent, + GenericReaction, + ReactionSet, + type RaisedHandInfo, + type ReactionInfo, +} from "."; + +export const REACTION_ACTIVE_TIME_MS = 3000; + +/** + * Listens for reactions from a RTCSession and populates subjects + * for consumption by the CallViewModel. + * @param rtcSession + */ +export class ReactionsReader { + private readonly raisedHandsSubject$ = new BehaviorSubject< + Record + >({}); + private readonly reactionsSubject$ = new BehaviorSubject< + Record + >({}); + + /** + * The latest set of raised hands. + */ + public readonly raisedHands$ = this.raisedHandsSubject$.asObservable(); + + /** + * The latest set of reactions. + */ + public readonly reactions$ = this.reactionsSubject$.asObservable(); + + private readonly reactionsSub: Subscription; + + public constructor(private readonly rtcSession: MatrixRTCSession) { + // Hide reactions after a given time. + this.reactionsSub = this.reactionsSubject$ + .pipe(delay(REACTION_ACTIVE_TIME_MS)) + .subscribe((reactions) => { + const date = new Date(); + const nextEntries = Object.fromEntries( + Object.entries(reactions).filter(([_, hr]) => hr.expireAfter > date), + ); + if (Object.keys(reactions).length === Object.keys(nextEntries).length) { + return; + } + this.reactionsSubject$.next(nextEntries); + }); + + this.rtcSession.room.on(MatrixRoomEvent.Timeline, this.handleReactionEvent); + this.rtcSession.room.on( + MatrixRoomEvent.Redaction, + this.handleReactionEvent, + ); + this.rtcSession.room.client.on( + MatrixEventEvent.Decrypted, + this.handleReactionEvent, + ); + + // We listen for a local echo to get the real event ID, as timeline events + // may still be sending. + this.rtcSession.room.on( + MatrixRoomEvent.LocalEchoUpdated, + this.handleReactionEvent, + ); + + rtcSession.on( + MatrixRTCSessionEvent.MembershipsChanged, + this.onMembershipsChanged, + ); + + // Run this once to ensure we have fetched the state from the call. + this.onMembershipsChanged([]); + } + + /** + * Fetchest any hand wave reactions by the given sender on the given + * membership event. + * @param membershipEventId + * @param expectedSender + * @returns A MatrixEvent if one was found. + */ + private getLastReactionEvent( + membershipEventId: string, + expectedSender: string, + ): MatrixEvent | undefined { + const relations = this.rtcSession.room.relations.getChildEventsForEvent( + membershipEventId, + RelationType.Annotation, + EventType.Reaction, + ); + const allEvents = relations?.getRelations() ?? []; + return allEvents.find( + (reaction) => + reaction.event.sender === expectedSender && + reaction.getType() === EventType.Reaction && + reaction.getContent()?.["m.relates_to"]?.key === "🖐️", + ); + } + + /** + * Will remove any hand raises by old members, and look for any + * existing hand raises by new members. + * @param oldMemberships Any members who have left the call. + */ + private onMembershipsChanged = (oldMemberships: CallMembership[]): void => { + // Remove any raised hands for users no longer joined to the call. + for (const identifier of Object.keys(this.raisedHandsSubject$.value).filter( + (rhId) => oldMemberships.find((u) => u.sender == rhId), + )) { + this.removeRaisedHand(identifier); + } + + // For each member in the call, check to see if a reaction has + // been raised and adjust. + for (const m of this.rtcSession.memberships) { + if (!m.sender || !m.eventId) { + continue; + } + const identifier = `${m.sender}:${m.deviceId}`; + if ( + this.raisedHandsSubject$.value[identifier] && + this.raisedHandsSubject$.value[identifier].membershipEventId !== + m.eventId + ) { + // Membership event for sender has changed since the hand + // was raised, reset. + this.removeRaisedHand(identifier); + } + const reaction = this.getLastReactionEvent(m.eventId, m.sender); + if (reaction) { + const eventId = reaction?.getId(); + if (!eventId) { + continue; + } + this.addRaisedHand(`${m.sender}:${m.deviceId}`, { + membershipEventId: m.eventId, + reactionEventId: eventId, + time: new Date(reaction.localTimestamp), + }); + } + } + }; + + /** + * Add a raised hand + * @param identifier A userId:deviceId combination. + * @param info The event information. + */ + private addRaisedHand(identifier: string, info: RaisedHandInfo): void { + this.raisedHandsSubject$.next({ + ...this.raisedHandsSubject$.value, + [identifier]: info, + }); + } + + /** + * Remove a raised hand + * @param identifier A userId:deviceId combination. + */ + private removeRaisedHand(identifier: string): void { + this.raisedHandsSubject$.next( + Object.fromEntries( + Object.entries(this.raisedHandsSubject$.value).filter( + ([uId]) => uId !== identifier, + ), + ), + ); + } + + /** + * Handle a new reaction event, validating it's contents and potentially + * updating the hand raise or reaction observers. + * @param event The incoming matrix event, which may or may not be decrypted. + */ + private handleReactionEvent = (event: MatrixEvent): void => { + const room = this.rtcSession.room; + // Decrypted events might come from a different room + if (event.getRoomId() !== room.roomId) return; + // Skip any events that are still sending. + if (event.isSending()) return; + + const sender = event.getSender(); + const reactionEventId = event.getId(); + // Skip any event without a sender or event ID. + if (!sender || !reactionEventId) return; + + room.client + .decryptEventIfNeeded(event) + .catch((e) => logger.warn(`Failed to decrypt ${event.getId()}`, e)); + if (event.isBeingDecrypted() || event.isDecryptionFailure()) return; + + if (event.getType() === ElementCallReactionEventType) { + const content: ECallReactionEventContent = event.getContent(); + + const membershipEventId = content?.["m.relates_to"]?.event_id; + const membershipEvent = this.rtcSession.memberships.find( + (e) => e.eventId === membershipEventId && e.sender === sender, + ); + // Check to see if this reaction was made to a membership event (and the + // sender of the reaction matches the membership) + if (!membershipEvent) { + logger.warn( + `Reaction target was not a membership event for ${sender}, ignoring`, + ); + return; + } + const identifier = `${membershipEvent.sender}:${membershipEvent.deviceId}`; + + if (!content.emoji) { + logger.warn(`Reaction had no emoji from ${reactionEventId}`); + return; + } + + const segment = new Intl.Segmenter(undefined, { + granularity: "grapheme", + }) + .segment(content.emoji) + [Symbol.iterator](); + const emoji = segment.next().value?.segment; + + if (!emoji?.trim()) { + logger.warn( + `Reaction had no emoji from ${reactionEventId} after splitting`, + ); + return; + } + + // One of our custom reactions + const reaction = { + ...GenericReaction, + emoji, + // If we don't find a reaction, we can fallback to the generic sound. + ...ReactionSet.find((r) => r.name === content.name), + }; + + const currentReactions = this.reactionsSubject$.value; + if (currentReactions[identifier]) { + // We've still got a reaction from this user, ignore it to prevent spamming + logger.warn(`Got reaction from ${identifier} but one is still playing`); + return; + } + this.reactionsSubject$.next({ + ...currentReactions, + [identifier]: { + reactionOption: reaction, + expireAfter: new Date(Date.now() + REACTION_ACTIVE_TIME_MS), + }, + }); + } else if (event.getType() === EventType.Reaction) { + const content = event.getContent() as ReactionEventContent; + const membershipEventId = content["m.relates_to"].event_id; + + // Check to see if this reaction was made to a membership event (and the + // sender of the reaction matches the membership) + const membershipEvent = this.rtcSession.memberships.find( + (e) => e.eventId === membershipEventId && e.sender === sender, + ); + if (!membershipEvent) { + logger.warn( + `Reaction target was not a membership event for ${sender}, ignoring`, + ); + return; + } + + if (content?.["m.relates_to"].key === "🖐️") { + this.addRaisedHand( + `${membershipEvent.sender}:${membershipEvent.deviceId}`, + { + reactionEventId, + membershipEventId, + time: new Date(event.localTimestamp), + }, + ); + } + } else if (event.getType() === EventType.RoomRedaction) { + const targetEvent = event.event.redacts; + const targetUser = Object.entries(this.raisedHandsSubject$.value).find( + ([_u, r]) => r.reactionEventId === targetEvent, + )?.[0]; + if (!targetUser) { + // Reaction target was not for us, ignoring + return; + } + this.removeRaisedHand(targetUser); + } + }; + + /** + * Stop listening for events. + */ + public destroy(): void { + this.rtcSession.off( + MatrixRTCSessionEvent.MembershipsChanged, + this.onMembershipsChanged, + ); + this.rtcSession.room.off( + MatrixRoomEvent.Timeline, + this.handleReactionEvent, + ); + this.rtcSession.room.off( + MatrixRoomEvent.Redaction, + this.handleReactionEvent, + ); + this.rtcSession.room.client.off( + MatrixEventEvent.Decrypted, + this.handleReactionEvent, + ); + this.rtcSession.room.off( + MatrixRoomEvent.LocalEchoUpdated, + this.handleReactionEvent, + ); + this.reactionsSub.unsubscribe(); + } +} diff --git a/src/reactions/index.ts b/src/reactions/index.ts index f8253c81..f20b9340 100644 --- a/src/reactions/index.ts +++ b/src/reactions/index.ts @@ -181,3 +181,23 @@ export const ReactionSet: ReactionOption[] = [ }, }, ]; + +export interface RaisedHandInfo { + /** + * Call membership event that was reacted to. + */ + membershipEventId: string; + /** + * Event ID of the reaction itself. + */ + reactionEventId: string; + /** + * The time when the reaction was raised. + */ + time: Date; +} + +export interface ReactionInfo { + expireAfter: Date; + reactionOption: ReactionOption; +} diff --git a/src/reactions/useReactionsSender.tsx b/src/reactions/useReactionsSender.tsx new file mode 100644 index 00000000..cb70bd87 --- /dev/null +++ b/src/reactions/useReactionsSender.tsx @@ -0,0 +1,174 @@ +/* +Copyright 2024 Milton Moura + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { EventType, RelationType } from "matrix-js-sdk/src/matrix"; +import { + createContext, + useContext, + type ReactNode, + useCallback, + useMemo, +} from "react"; +import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { logger } from "matrix-js-sdk/src/logger"; +import { useObservableEagerState } from "observable-hooks"; + +import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships"; +import { useClientState } from "../ClientContext"; +import { ElementCallReactionEventType, type ReactionOption } from "."; +import { type CallViewModel } from "../state/CallViewModel"; + +interface ReactionsSenderContextType { + supportsReactions: boolean; + toggleRaisedHand: () => Promise; + sendReaction: (reaction: ReactionOption) => Promise; +} + +const ReactionsSenderContext = createContext< + ReactionsSenderContextType | undefined +>(undefined); + +export const useReactionsSender = (): ReactionsSenderContextType => { + const context = useContext(ReactionsSenderContext); + if (!context) { + throw new Error("useReactions must be used within a ReactionsProvider"); + } + return context; +}; + +/** + * Provider that handles sending a reaction or hand raised event to a call. + */ +export const ReactionsSenderProvider = ({ + children, + rtcSession, + vm, +}: { + children: ReactNode; + rtcSession: MatrixRTCSession; + vm: CallViewModel; +}): JSX.Element => { + const memberships = useMatrixRTCSessionMemberships(rtcSession); + const clientState = useClientState(); + const supportsReactions = + clientState?.state === "valid" && clientState.supportedFeatures.reactions; + const room = rtcSession.room; + const myUserId = room.client.getUserId(); + const myDeviceId = room.client.getDeviceId(); + + const myMembershipEvent = useMemo( + () => + memberships.find( + (m) => m.sender === myUserId && m.deviceId === myDeviceId, + )?.eventId, + [memberships, myUserId, myDeviceId], + ); + const myMembershipIdentifier = useMemo(() => { + const membership = memberships.find((m) => m.sender === myUserId); + return membership + ? `${membership.sender}:${membership.deviceId}` + : undefined; + }, [memberships, myUserId]); + + const reactions = useObservableEagerState(vm.reactions$); + const myReaction = useMemo( + () => + myMembershipIdentifier !== undefined + ? reactions[myMembershipIdentifier] + : undefined, + [myMembershipIdentifier, reactions], + ); + + const handsRaised = useObservableEagerState(vm.handsRaised$); + const myRaisedHand = useMemo( + () => + myMembershipIdentifier !== undefined + ? handsRaised[myMembershipIdentifier] + : undefined, + [myMembershipIdentifier, handsRaised], + ); + + const toggleRaisedHand = useCallback(async () => { + if (!myMembershipIdentifier) { + return; + } + const myReactionId = myRaisedHand?.reactionEventId; + + if (!myReactionId) { + try { + if (!myMembershipEvent) { + throw new Error("Cannot find own membership event"); + } + const reaction = await room.client.sendEvent( + rtcSession.room.roomId, + EventType.Reaction, + { + "m.relates_to": { + rel_type: RelationType.Annotation, + event_id: myMembershipEvent, + key: "🖐️", + }, + }, + ); + logger.debug("Sent raise hand event", reaction.event_id); + } catch (ex) { + logger.error("Failed to send raised hand", ex); + } + } else { + try { + await room.client.redactEvent(rtcSession.room.roomId, myReactionId); + logger.debug("Redacted raise hand event"); + } catch (ex) { + logger.error("Failed to redact reaction event", myReactionId, ex); + throw ex; + } + } + }, [ + myMembershipEvent, + myMembershipIdentifier, + myRaisedHand, + rtcSession, + room, + ]); + + const sendReaction = useCallback( + async (reaction: ReactionOption) => { + if (!myMembershipIdentifier || myReaction) { + // We're still reacting + return; + } + if (!myMembershipEvent) { + throw new Error("Cannot find own membership event"); + } + await room.client.sendEvent( + rtcSession.room.roomId, + ElementCallReactionEventType, + { + "m.relates_to": { + rel_type: RelationType.Reference, + event_id: myMembershipEvent, + }, + emoji: reaction.emoji, + name: reaction.name, + }, + ); + }, + [myMembershipEvent, myReaction, room, myMembershipIdentifier, rtcSession], + ); + + return ( + + {children} + + ); +}; diff --git a/src/room/CallEventAudioRenderer.test.tsx b/src/room/CallEventAudioRenderer.test.tsx index cc7f4eea..10fcbecf 100644 --- a/src/room/CallEventAudioRenderer.test.tsx +++ b/src/room/CallEventAudioRenderer.test.tsx @@ -15,43 +15,23 @@ import { vitest, afterEach, } from "vitest"; -import { type MatrixClient } from "matrix-js-sdk/src/client"; -import { ConnectionState } from "livekit-client"; -import { BehaviorSubject, of } from "rxjs"; -import { act, type ReactNode } from "react"; -import { - type CallMembership, - type MatrixRTCSession, -} from "matrix-js-sdk/src/matrixrtc"; -import { type RoomMember } from "matrix-js-sdk/src/matrix"; +import { act } from "react"; +import { type CallMembership } from "matrix-js-sdk/src/matrixrtc"; -import { - mockLivekitRoom, - mockLocalParticipant, - mockMatrixRoom, - mockMatrixRoomMember, - mockRemoteParticipant, - mockRtcMembership, - MockRTCSession, -} from "../utils/test"; -import { E2eeType } from "../e2ee/e2eeType"; -import { CallViewModel } from "../state/CallViewModel"; +import { mockRtcMembership } from "../utils/test"; import { CallEventAudioRenderer, MAX_PARTICIPANT_COUNT_FOR_SOUND, } from "./CallEventAudioRenderer"; import { useAudioContext } from "../useAudioContext"; -import { TestReactionsWrapper } from "../utils/testReactions"; import { prefetchSounds } from "../soundUtils"; - -const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); -const local = mockMatrixRoomMember(localRtcMember); -const aliceRtcMember = mockRtcMembership("@alice:example.org", "AAAA"); -const alice = mockMatrixRoomMember(aliceRtcMember); -const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB"); -const localParticipant = mockLocalParticipant({ identity: "" }); -const aliceId = `${alice.userId}:${aliceRtcMember.deviceId}`; -const aliceParticipant = mockRemoteParticipant({ identity: aliceId }); +import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel"; +import { + alice, + aliceRtcMember, + bobRtcMember, + local, +} from "../utils/test-fixtures"; vitest.mock("../useAudioContext"); vitest.mock("../soundUtils"); @@ -78,66 +58,6 @@ beforeEach(() => { }); }); -function TestComponent({ - rtcSession, - vm, -}: { - rtcSession: MockRTCSession; - vm: CallViewModel; -}): ReactNode { - return ( - - - - ); -} - -function getMockEnv( - members: RoomMember[], - initialRemoteRtcMemberships: CallMembership[] = [aliceRtcMember], -): { - vm: CallViewModel; - session: MockRTCSession; - remoteRtcMemberships$: BehaviorSubject; -} { - const matrixRoomMembers = new Map(members.map((p) => [p.userId, p])); - const remoteParticipants$ = of([aliceParticipant]); - const liveKitRoom = mockLivekitRoom( - { localParticipant }, - { remoteParticipants$ }, - ); - const matrixRoom = mockMatrixRoom({ - client: { - getUserId: () => localRtcMember.sender, - getDeviceId: () => localRtcMember.deviceId, - on: vitest.fn(), - off: vitest.fn(), - } as Partial as MatrixClient, - getMember: (userId) => matrixRoomMembers.get(userId) ?? null, - }); - - const remoteRtcMemberships$ = new BehaviorSubject( - initialRemoteRtcMemberships, - ); - - const session = new MockRTCSession( - matrixRoom, - localRtcMember, - ).withMemberships(remoteRtcMemberships$); - - const vm = new CallViewModel( - session as unknown as MatrixRTCSession, - liveKitRoom, - { - kind: E2eeType.PER_PARTICIPANT, - }, - of(ConnectionState.Connected), - ); - return { vm, session, remoteRtcMemberships$ }; -} - /** * We don't want to play a sound when loading the call state * because typically this occurs in two stages. We first join @@ -146,8 +66,12 @@ function getMockEnv( * a noise every time. */ test("plays one sound when entering a call", () => { - const { session, vm, remoteRtcMemberships$ } = getMockEnv([local, alice]); - render(); + const { vm, remoteRtcMemberships$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + render(); + // Joining a call usually means remote participants are added later. act(() => { remoteRtcMemberships$.next([aliceRtcMember, bobRtcMember]); @@ -155,10 +79,12 @@ test("plays one sound when entering a call", () => { expect(playSound).toHaveBeenCalledOnce(); }); -// TODO: Same test? test("plays a sound when a user joins", () => { - const { session, vm, remoteRtcMemberships$ } = getMockEnv([local, alice]); - render(); + const { vm, remoteRtcMemberships$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + render(); act(() => { remoteRtcMemberships$.next([aliceRtcMember, bobRtcMember]); @@ -168,8 +94,11 @@ test("plays a sound when a user joins", () => { }); test("plays a sound when a user leaves", () => { - const { session, vm, remoteRtcMemberships$ } = getMockEnv([local, alice]); - render(); + const { vm, remoteRtcMemberships$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + render(); act(() => { remoteRtcMemberships$.next([]); @@ -185,12 +114,12 @@ test("plays no sound when the participant list is more than the maximum size", ( ); } - const { session, vm, remoteRtcMemberships$ } = getMockEnv( + const { vm, remoteRtcMemberships$ } = getBasicCallViewModelEnvironment( [local, alice], mockRtcMemberships, ); - render(); + render(); expect(playSound).not.toBeCalled(); act(() => { remoteRtcMemberships$.next( @@ -199,3 +128,56 @@ test("plays no sound when the participant list is more than the maximum size", ( }); expect(playSound).toBeCalledWith("left"); }); + +test("plays one sound when a hand is raised", () => { + const { vm, handRaisedSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + render(); + + act(() => { + handRaisedSubject$.next({ + [bobRtcMember.callId]: { + time: new Date(), + membershipEventId: "", + reactionEventId: "", + }, + }); + }); + expect(playSound).toBeCalledWith("raiseHand"); +}); + +test("should not play a sound when a hand raise is retracted", () => { + const { vm, handRaisedSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + render(); + + act(() => { + handRaisedSubject$.next({ + ["foo"]: { + time: new Date(), + membershipEventId: "", + reactionEventId: "", + }, + ["bar"]: { + time: new Date(), + membershipEventId: "", + reactionEventId: "", + }, + }); + }); + expect(playSound).toHaveBeenCalledTimes(2); + act(() => { + handRaisedSubject$.next({ + ["foo"]: { + time: new Date(), + membershipEventId: "", + reactionEventId: "", + }, + }); + }); + expect(playSound).toHaveBeenCalledTimes(2); +}); diff --git a/src/room/CallEventAudioRenderer.tsx b/src/room/CallEventAudioRenderer.tsx index 97270791..afc5132b 100644 --- a/src/room/CallEventAudioRenderer.tsx +++ b/src/room/CallEventAudioRenderer.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { type ReactNode, useDeferredValue, useEffect, useMemo } from "react"; +import { type ReactNode, useEffect } from "react"; import { filter, interval, throttle } from "rxjs"; import { type CallViewModel } from "../state/CallViewModel"; @@ -19,7 +19,6 @@ import screenShareStartedOgg from "../sound/screen_share_started.ogg"; import screenShareStartedMp3 from "../sound/screen_share_started.mp3"; import { useAudioContext } from "../useAudioContext"; import { prefetchSounds } from "../soundUtils"; -import { useReactions } from "../useReactions"; import { useLatest } from "../useLatest"; // Do not play any sounds if the participant count has exceeded this @@ -57,19 +56,6 @@ export function CallEventAudioRenderer({ }); const audioEngineRef = useLatest(audioEngineCtx); - const { raisedHands } = useReactions(); - const raisedHandCount = useMemo( - () => Object.keys(raisedHands).length, - [raisedHands], - ); - const previousRaisedHandCount = useDeferredValue(raisedHandCount); - - useEffect(() => { - if (audioEngineRef.current && previousRaisedHandCount < raisedHandCount) { - void audioEngineRef.current.playSound("raiseHand"); - } - }, [audioEngineRef, previousRaisedHandCount, raisedHandCount]); - useEffect(() => { const joinSub = vm.memberChanges$ .pipe( @@ -95,6 +81,10 @@ export function CallEventAudioRenderer({ void audioEngineRef.current?.playSound("left"); }); + const handRaisedSub = vm.newHandRaised$.subscribe(() => { + void audioEngineRef.current?.playSound("raiseHand"); + }); + const screenshareSub = vm.newScreenShare$.subscribe(() => { void audioEngineRef.current?.playSound("screenshareStarted"); }); @@ -102,6 +92,7 @@ export function CallEventAudioRenderer({ return (): void => { joinSub.unsubscribe(); leftSub.unsubscribe(); + handRaisedSub.unsubscribe(); screenshareSub.unsubscribe(); }; }, [audioEngineRef, vm]); diff --git a/src/room/GroupCallView.test.tsx b/src/room/GroupCallView.test.tsx index ea2cc5cf..b1cb53f0 100644 --- a/src/room/GroupCallView.test.tsx +++ b/src/room/GroupCallView.test.tsx @@ -14,6 +14,7 @@ import { JoinRule, type RoomState } from "matrix-js-sdk/src/matrix"; import { Router } from "react-router-dom"; import { createBrowserHistory } from "history"; import userEvent from "@testing-library/user-event"; +import { type RelationsContainer } from "matrix-js-sdk/src/models/relations-container"; import { type MuteStates } from "./MuteStates"; import { prefetchSounds } from "../soundUtils"; @@ -85,6 +86,12 @@ function createGroupCallView(widget: WidgetHelpers | null): { getRoom: (rId) => (rId === roomId ? room : null), } as Partial as MatrixClient; const room = mockMatrixRoom({ + relations: { + getChildEventsForEvent: () => + vitest.mocked({ + getRelations: () => [], + }), + } as unknown as RelationsContainer, client, roomId, getMember: (userId) => roomMembers.get(userId) ?? null, diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 3ea6a9c2..cf587fcd 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -366,7 +366,7 @@ export const GroupCallView: FC = ({ = (props) => { useEffect(() => { if (livekitRoom !== undefined) { + const reactionsReader = new ReactionsReader(props.rtcSession); const vm = new CallViewModel( props.rtcSession, livekitRoom, props.e2eeSystem, connStateObservable$, + reactionsReader.raisedHands$, + reactionsReader.reactions$, ); setVm(vm); - return (): void => vm.destroy(); + return (): void => { + vm.destroy(); + reactionsReader.destroy(); + }; } }, [props.rtcSession, livekitRoom, props.e2eeSystem, connStateObservable$]); @@ -142,14 +152,14 @@ export const ActiveCall: FC = (props) => { return ( - + - + ); }; @@ -182,7 +192,8 @@ export const InCallView: FC = ({ connState, onShareClick, }) => { - const { supportsReactions, sendReaction, toggleRaisedHand } = useReactions(); + const { supportsReactions, sendReaction, toggleRaisedHand } = + useReactionsSender(); useWakeLock(); @@ -551,9 +562,10 @@ export const InCallView: FC = ({ if (supportsReactions) { buttons.push( , ); @@ -653,8 +665,8 @@ export const InCallView: FC = ({ {renderContent()} - - + + {footer} {layout.type !== "pip" && ( <> diff --git a/src/room/ReactionAudioRenderer.test.tsx b/src/room/ReactionAudioRenderer.test.tsx index afa2c6ff..ec71571c 100644 --- a/src/room/ReactionAudioRenderer.test.tsx +++ b/src/room/ReactionAudioRenderer.test.tsx @@ -19,11 +19,6 @@ import { import { TooltipProvider } from "@vector-im/compound-web"; import { act, type ReactNode } from "react"; -import { - MockRoom, - MockRTCSession, - TestReactionsWrapper, -} from "../utils/testReactions"; import { ReactionsAudioRenderer } from "./ReactionAudioRenderer"; import { playReactionsSound, @@ -32,30 +27,20 @@ import { import { useAudioContext } from "../useAudioContext"; import { GenericReaction, ReactionSet } from "../reactions"; import { prefetchSounds } from "../soundUtils"; +import { type CallViewModel } from "../state/CallViewModel"; +import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel"; +import { + alice, + aliceRtcMember, + bobRtcMember, + local, + localRtcMember, +} from "../utils/test-fixtures"; -const memberUserIdAlice = "@alice:example.org"; -const memberUserIdBob = "@bob:example.org"; -const memberUserIdCharlie = "@charlie:example.org"; -const memberEventAlice = "$membership-alice:example.org"; -const memberEventBob = "$membership-bob:example.org"; -const memberEventCharlie = "$membership-charlie:example.org"; - -const membership: Record = { - [memberEventAlice]: memberUserIdAlice, - [memberEventBob]: memberUserIdBob, - [memberEventCharlie]: memberUserIdCharlie, -}; - -function TestComponent({ - rtcSession, -}: { - rtcSession: MockRTCSession; -}): ReactNode { +function TestComponent({ vm }: { vm: CallViewModel }): ReactNode { return ( - - - + ); } @@ -88,20 +73,19 @@ beforeEach(() => { }); test("preloads all audio elements", () => { + const { vm } = getBasicCallViewModelEnvironment([local, alice]); playReactionsSound.setValue(true); - const rtcSession = new MockRTCSession( - new MockRoom(memberUserIdAlice), - membership, - ); - render(); + render(); expect(prefetchSounds).toHaveBeenCalledOnce(); }); test("will play an audio sound when there is a reaction", () => { + const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); playReactionsSound.setValue(true); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - render(); + render(); // Find the first reaction with a sound effect const chosenReaction = ReactionSet.find((r) => !!r.sound); @@ -111,16 +95,23 @@ test("will play an audio sound when there is a reaction", () => { ); } act(() => { - room.testSendReaction(memberEventAlice, chosenReaction, membership); + reactionsSubject$.next({ + [aliceRtcMember.deviceId]: { + reactionOption: chosenReaction, + expireAfter: new Date(0), + }, + }); }); expect(playSound).toHaveBeenCalledWith(chosenReaction.name); }); test("will play the generic audio sound when there is soundless reaction", () => { + const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); playReactionsSound.setValue(true); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - render(); + render(); // Find the first reaction with a sound effect const chosenReaction = ReactionSet.find((r) => !r.sound); @@ -130,17 +121,23 @@ test("will play the generic audio sound when there is soundless reaction", () => ); } act(() => { - room.testSendReaction(memberEventAlice, chosenReaction, membership); + reactionsSubject$.next({ + [aliceRtcMember.deviceId]: { + reactionOption: chosenReaction, + expireAfter: new Date(0), + }, + }); }); expect(playSound).toHaveBeenCalledWith(GenericReaction.name); }); test("will play multiple audio sounds when there are multiple different reactions", () => { + const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); playReactionsSound.setValue(true); - - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - render(); + render(); // Find the first reaction with a sound effect const [reaction1, reaction2] = ReactionSet.filter((r) => !!r.sound); @@ -150,9 +147,20 @@ test("will play multiple audio sounds when there are multiple different reaction ); } act(() => { - room.testSendReaction(memberEventAlice, reaction1, membership); - room.testSendReaction(memberEventBob, reaction2, membership); - room.testSendReaction(memberEventCharlie, reaction1, membership); + reactionsSubject$.next({ + [aliceRtcMember.deviceId]: { + reactionOption: reaction1, + expireAfter: new Date(0), + }, + [bobRtcMember.deviceId]: { + reactionOption: reaction2, + expireAfter: new Date(0), + }, + [localRtcMember.deviceId]: { + reactionOption: reaction1, + expireAfter: new Date(0), + }, + }); }); expect(playSound).toHaveBeenCalledWith(reaction1.name); expect(playSound).toHaveBeenCalledWith(reaction2.name); diff --git a/src/room/ReactionAudioRenderer.tsx b/src/room/ReactionAudioRenderer.tsx index be24a5d6..1b33d65a 100644 --- a/src/room/ReactionAudioRenderer.tsx +++ b/src/room/ReactionAudioRenderer.tsx @@ -5,14 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { type ReactNode, useDeferredValue, useEffect, useState } from "react"; +import { type ReactNode, useEffect, useState } from "react"; -import { useReactions } from "../useReactions"; import { playReactionsSound, useSetting } from "../settings/settings"; import { GenericReaction, ReactionSet } from "../reactions"; import { useAudioContext } from "../useAudioContext"; import { prefetchSounds } from "../soundUtils"; import { useLatest } from "../useLatest"; +import { type CallViewModel } from "../state/CallViewModel"; const soundMap = Object.fromEntries([ ...ReactionSet.filter((v) => v.sound !== undefined).map((v) => [ @@ -22,8 +22,11 @@ const soundMap = Object.fromEntries([ [GenericReaction.name, GenericReaction.sound], ]); -export function ReactionsAudioRenderer(): ReactNode { - const { reactions } = useReactions(); +export function ReactionsAudioRenderer({ + vm, +}: { + vm: CallViewModel; +}): ReactNode { const [shouldPlay] = useSetting(playReactionsSound); const [soundCache, setSoundCache] = useState { if (!shouldPlay || soundCache) { @@ -46,26 +48,19 @@ export function ReactionsAudioRenderer(): ReactNode { }, [soundCache, shouldPlay]); useEffect(() => { - if (!shouldPlay || !audioEngineRef.current) { - return; - } - const oldReactionSet = new Set( - Object.values(oldReactions).map((r) => r.name), - ); - for (const reactionName of new Set( - Object.values(reactions).map((r) => r.name), - )) { - if (oldReactionSet.has(reactionName)) { - // Don't replay old reactions - return; + const sub = vm.audibleReactions$.subscribe((newReactions) => { + for (const reactionName of newReactions) { + if (soundMap[reactionName]) { + void audioEngineRef.current?.playSound(reactionName); + } else { + // Fallback sounds. + void audioEngineRef.current?.playSound("generic"); + } } - if (soundMap[reactionName]) { - void audioEngineRef.current.playSound(reactionName); - } else { - // Fallback sounds. - void audioEngineRef.current.playSound("generic"); - } - } - }, [audioEngineRef, shouldPlay, oldReactions, reactions]); + }); + return (): void => { + sub.unsubscribe(); + }; + }, [vm, audioEngineRef]); return null; } diff --git a/src/room/ReactionsOverlay.test.tsx b/src/room/ReactionsOverlay.test.tsx index 5c3f8bf9..77ec77f8 100644 --- a/src/room/ReactionsOverlay.test.tsx +++ b/src/room/ReactionsOverlay.test.tsx @@ -7,44 +7,18 @@ Please see LICENSE in the repository root for full details. import { render } from "@testing-library/react"; import { expect, test, afterEach } from "vitest"; -import { TooltipProvider } from "@vector-im/compound-web"; -import { act, type ReactNode } from "react"; +import { act } from "react"; -import { - MockRoom, - MockRTCSession, - TestReactionsWrapper, -} from "../utils/testReactions"; import { showReactions } from "../settings/settings"; import { ReactionsOverlay } from "./ReactionsOverlay"; import { ReactionSet } from "../reactions"; - -const memberUserIdAlice = "@alice:example.org"; -const memberUserIdBob = "@bob:example.org"; -const memberUserIdCharlie = "@charlie:example.org"; -const memberEventAlice = "$membership-alice:example.org"; -const memberEventBob = "$membership-bob:example.org"; -const memberEventCharlie = "$membership-charlie:example.org"; - -const membership: Record = { - [memberEventAlice]: memberUserIdAlice, - [memberEventBob]: memberUserIdBob, - [memberEventCharlie]: memberUserIdCharlie, -}; - -function TestComponent({ - rtcSession, -}: { - rtcSession: MockRTCSession; -}): ReactNode { - return ( - - - - - - ); -} +import { + local, + alice, + aliceRtcMember, + bobRtcMember, +} from "../utils/test-fixtures"; +import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel"; afterEach(() => { showReactions.setValue(showReactions.defaultValue); @@ -52,22 +26,26 @@ afterEach(() => { test("defaults to showing no reactions", () => { showReactions.setValue(true); - const rtcSession = new MockRTCSession( - new MockRoom(memberUserIdAlice), - membership, - ); - const { container } = render(); + const { vm } = getBasicCallViewModelEnvironment([local, alice]); + const { container } = render(); expect(container.getElementsByTagName("span")).toHaveLength(0); }); test("shows a reaction when sent", () => { showReactions.setValue(true); + const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + const { getByRole } = render(); const reaction = ReactionSet[0]; - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { getByRole } = render(); act(() => { - room.testSendReaction(memberEventAlice, reaction, membership); + reactionsSubject$.next({ + [aliceRtcMember.deviceId]: { + reactionOption: reaction, + expireAfter: new Date(0), + }, + }); }); const span = getByRole("presentation"); expect(getByRole("presentation")).toBeTruthy(); @@ -77,29 +55,45 @@ test("shows a reaction when sent", () => { test("shows two of the same reaction when sent", () => { showReactions.setValue(true); const reaction = ReactionSet[0]; - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { getAllByRole } = render(); + const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + const { getAllByRole } = render(); act(() => { - room.testSendReaction(memberEventAlice, reaction, membership); - }); - act(() => { - room.testSendReaction(memberEventBob, reaction, membership); + reactionsSubject$.next({ + [aliceRtcMember.deviceId]: { + reactionOption: reaction, + expireAfter: new Date(0), + }, + [bobRtcMember.deviceId]: { + reactionOption: reaction, + expireAfter: new Date(0), + }, + }); }); expect(getAllByRole("presentation")).toHaveLength(2); }); test("shows two different reactions when sent", () => { showReactions.setValue(true); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); const [reactionA, reactionB] = ReactionSet; - const { getAllByRole } = render(); + const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + const { getAllByRole } = render(); act(() => { - room.testSendReaction(memberEventAlice, reactionA, membership); - }); - act(() => { - room.testSendReaction(memberEventBob, reactionB, membership); + reactionsSubject$.next({ + [aliceRtcMember.deviceId]: { + reactionOption: reactionA, + expireAfter: new Date(0), + }, + [bobRtcMember.deviceId]: { + reactionOption: reactionB, + expireAfter: new Date(0), + }, + }); }); const [reactionElementA, reactionElementB] = getAllByRole("presentation"); expect(reactionElementA.innerHTML).toEqual(reactionA.emoji); @@ -109,11 +103,18 @@ test("shows two different reactions when sent", () => { test("hides reactions when reaction animations are disabled", () => { showReactions.setValue(false); const reaction = ReactionSet[0]; - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); + const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([ + local, + alice, + ]); + const { container } = render(); act(() => { - room.testSendReaction(memberEventAlice, reaction, membership); + reactionsSubject$.next({ + [aliceRtcMember.deviceId]: { + reactionOption: reaction, + expireAfter: new Date(0), + }, + }); }); - const { container } = render(); expect(container.getElementsByTagName("span")).toHaveLength(0); }); diff --git a/src/room/ReactionsOverlay.tsx b/src/room/ReactionsOverlay.tsx index 2f8daba5..e642a16c 100644 --- a/src/room/ReactionsOverlay.tsx +++ b/src/room/ReactionsOverlay.tsx @@ -5,33 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { type ReactNode, useMemo } from "react"; +import { type ReactNode } from "react"; +import { useObservableState } from "observable-hooks"; -import { useReactions } from "../useReactions"; -import { - showReactions as showReactionsSetting, - useSetting, -} from "../settings/settings"; import styles from "./ReactionsOverlay.module.css"; +import { type CallViewModel } from "../state/CallViewModel"; -export function ReactionsOverlay(): ReactNode { - const { reactions } = useReactions(); - const [showReactions] = useSetting(showReactionsSetting); - const reactionsIcons = useMemo( - () => - showReactions - ? Object.entries(reactions).map(([sender, { emoji }]) => ({ - sender, - emoji, - startX: Math.ceil(Math.random() * 80) + 10, - })) - : [], - [showReactions, reactions], - ); - +export function ReactionsOverlay({ vm }: { vm: CallViewModel }): ReactNode { + const reactionsIcons = useObservableState(vm.visibleReactions$); return (

- {reactionsIcons.map(({ sender, emoji, startX }) => ( + {reactionsIcons?.map(({ sender, emoji, startX }) => ( []>, connectionState$: Observable, speaking: Map>, - continuation: (vm: CallViewModel) => void, + continuation: ( + vm: CallViewModel, + subjects: { raisedHands$: BehaviorSubject> }, + ) => void, ): void { const room = mockMatrixRoom({ client: { @@ -235,6 +240,8 @@ function withCallViewModel( { remoteParticipants$ }, ); + const raisedHands$ = new BehaviorSubject>({}); + const vm = new CallViewModel( rtcSession as unknown as MatrixRTCSession, liveKitRoom, @@ -242,6 +249,8 @@ function withCallViewModel( kind: E2eeType.PER_PARTICIPANT, }, connectionState$, + raisedHands$, + new BehaviorSubject({}), ); onTestFinished(() => { @@ -252,7 +261,7 @@ function withCallViewModel( roomEventSelectorSpy!.mockRestore(); }); - continuation(vm); + continuation(vm, { raisedHands$: raisedHands$ }); } test("participants are retained during a focus switch", () => { @@ -782,3 +791,62 @@ it("should show at least one tile per MatrixRTCSession", () => { ); }); }); + +it("should rank raised hands above video feeds and below speakers and presenters", () => { + withTestScheduler(({ schedule, expectObservable }) => { + // There should always be one tile for each MatrixRTCSession + const expectedLayoutMarbles = "ab"; + + withCallViewModel( + of([aliceParticipant, bobParticipant]), + of([aliceRtcMember, bobRtcMember]), + of(ConnectionState.Connected), + new Map(), + (vm, { raisedHands$ }) => { + schedule("ab", { + a: () => { + // We imagine that only two tiles (the first two) will be visible on screen at a time + vm.layout$.subscribe((layout) => { + if (layout.type === "grid") { + layout.setVisibleTiles(2); + } + }); + }, + b: () => { + raisedHands$.next({ + [`${bobRtcMember.sender}:${bobRtcMember.deviceId}`]: { + time: new Date(), + reactionEventId: "", + membershipEventId: "", + }, + }); + }, + }); + expectObservable(summarizeLayout$(vm.layout$)).toBe( + expectedLayoutMarbles, + { + a: { + type: "grid", + spotlight: undefined, + grid: [ + "local:0", + "@alice:example.org:AAAA:0", + "@bob:example.org:BBBB:0", + ], + }, + b: { + type: "grid", + spotlight: undefined, + grid: [ + "local:0", + // Bob shifts up! + "@bob:example.org:BBBB:0", + "@alice:example.org:AAAA:0", + ], + }, + }, + ); + }, + ); + }); +}); diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 5f1526fd..0c3b80db 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -69,7 +69,12 @@ import { } from "./MediaViewModel"; import { accumulate, finalizeValue } from "../utils/observable"; import { ObservableScope } from "./ObservableScope"; -import { duplicateTiles, showNonMemberTiles } from "../settings/settings"; +import { + duplicateTiles, + playReactionsSound, + showReactions, + showNonMemberTiles, +} from "../settings/settings"; import { isFirefox } from "../Platform"; import { setPipEnabled$ } from "../controls"; import { @@ -82,6 +87,11 @@ import { spotlightExpandedLayout } from "./SpotlightExpandedLayout"; import { oneOnOneLayout } from "./OneOnOneLayout"; import { pipLayout } from "./PipLayout"; import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; +import { + type RaisedHandInfo, + type ReactionInfo, + type ReactionOption, +} from "../reactions"; import { observeSpeaker$ } from "./observeSpeaker"; import { shallowEquals } from "../utils/array"; @@ -210,6 +220,10 @@ enum SortingBin { * Participants that have been speaking recently. */ Speakers, + /** + * Participants that have their hand raised. + */ + HandRaised, /** * Participants with video. */ @@ -244,6 +258,8 @@ class UserMedia { participant: LocalParticipant | RemoteParticipant | undefined, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, + handRaised$: Observable, + reaction$: Observable, ) { this.participant$ = new BehaviorSubject(participant); @@ -254,6 +270,8 @@ class UserMedia { this.participant$.asObservable() as Observable, encryptionSystem, livekitRoom, + handRaised$, + reaction$, ); } else { this.vm = new RemoteUserMediaViewModel( @@ -264,6 +282,8 @@ class UserMedia { >, encryptionSystem, livekitRoom, + handRaised$, + reaction$, ); } @@ -473,6 +493,8 @@ export class CallViewModel extends ViewModel { let livekitParticipantId = rtcMember.sender + ":" + rtcMember.deviceId; + const matrixIdentifier = `${rtcMember.sender}:${rtcMember.deviceId}`; + let participant: | LocalParticipant | RemoteParticipant @@ -522,6 +544,12 @@ export class CallViewModel extends ViewModel { participant, this.encryptionSystem, this.livekitRoom, + this.handsRaised$.pipe( + map((v) => v[matrixIdentifier]?.time ?? null), + ), + this.reactions$.pipe( + map((v) => v[matrixIdentifier] ?? undefined), + ), ), ]; @@ -574,6 +602,8 @@ export class CallViewModel extends ViewModel { participant, this.encryptionSystem, this.livekitRoom, + of(null), + of(null), ), ]; } @@ -681,11 +711,12 @@ export class CallViewModel extends ViewModel { m.speaker$, m.presenter$, m.vm.videoEnabled$, + m.vm.handRaised$, m.vm instanceof LocalUserMediaViewModel ? m.vm.alwaysShow$ : of(false), ], - (speaker, presenter, video, alwaysShow) => { + (speaker, presenter, video, handRaised, alwaysShow) => { let bin: SortingBin; if (m.vm.local) bin = alwaysShow @@ -693,6 +724,7 @@ export class CallViewModel extends ViewModel { : SortingBin.SelfNotAlwaysShown; else if (presenter) bin = SortingBin.Presenters; else if (speaker) bin = SortingBin.Speakers; + else if (handRaised) bin = SortingBin.HandRaised; else if (video) bin = SortingBin.Video; else bin = SortingBin.NoVideo; @@ -1170,6 +1202,77 @@ export class CallViewModel extends ViewModel { }), this.scope.state(), ); + + public readonly reactions$ = this.reactionsSubject$.pipe( + map((v) => + Object.fromEntries( + Object.entries(v).map(([a, { reactionOption }]) => [a, reactionOption]), + ), + ), + ); + + public readonly handsRaised$ = this.handsRaisedSubject$.pipe(); + + /** + * Emits an array of reactions that should be visible on the screen. + */ + public readonly visibleReactions$ = showReactions.value$.pipe( + switchMap((show) => (show ? this.reactions$ : of({}))), + scan< + Record, + { sender: string; emoji: string; startX: number }[] + >((acc, latest) => { + const newSet: { sender: string; emoji: string; startX: number }[] = []; + for (const [sender, reaction] of Object.entries(latest)) { + const startX = + acc.find((v) => v.sender === sender && v.emoji)?.startX ?? + Math.ceil(Math.random() * 80) + 10; + newSet.push({ sender, emoji: reaction.emoji, startX }); + } + return newSet; + }, []), + ); + + /** + * Emits an array of reactions that should be played. + */ + public readonly audibleReactions$ = playReactionsSound.value$.pipe( + switchMap((show) => + show ? this.reactions$ : of>({}), + ), + map((reactions) => Object.values(reactions).map((v) => v.name)), + scan( + (acc, latest) => { + return { + playing: latest.filter( + (v) => acc.playing.includes(v) || acc.newSounds.includes(v), + ), + newSounds: latest.filter( + (v) => !acc.playing.includes(v) && !acc.newSounds.includes(v), + ), + }; + }, + { playing: [], newSounds: [] }, + ), + map((v) => v.newSounds), + ); + + /** + * Emits an event every time a new hand is raised in + * the call. + */ + public readonly newHandRaised$ = this.handsRaised$.pipe( + map((v) => Object.keys(v).length), + scan( + (acc, newValue) => ({ + value: newValue, + playSounds: newValue > acc.value, + }), + { value: 0, playSounds: false }, + ), + filter((v) => v.playSounds), + ); + /** * Emits an event every time a new screenshare is started in * the call. @@ -1192,6 +1295,12 @@ export class CallViewModel extends ViewModel { private readonly livekitRoom: LivekitRoom, private readonly encryptionSystem: EncryptionSystem, private readonly connectionState$: Observable, + private readonly handsRaisedSubject$: Observable< + Record + >, + private readonly reactionsSubject$: Observable< + Record + >, ) { super(); } diff --git a/src/state/MediaViewModel.ts b/src/state/MediaViewModel.ts index 8100a50d..b57b6f15 100644 --- a/src/state/MediaViewModel.ts +++ b/src/state/MediaViewModel.ts @@ -51,6 +51,7 @@ import { alwaysShowSelf } from "../settings/settings"; import { accumulate } from "../utils/observable"; import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; import { E2eeType } from "../e2ee/e2eeType"; +import { type ReactionOption } from "../reactions"; // TODO: Move this naming logic into the view model export function useDisplayName(vm: MediaViewModel): string { @@ -371,6 +372,8 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel { participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, + public readonly handRaised$: Observable, + public readonly reaction$: Observable, ) { super( id, @@ -437,8 +440,18 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel { participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, + handRaised$: Observable, + reaction$: Observable, ) { - super(id, member, participant$, encryptionSystem, livekitRoom); + super( + id, + member, + participant$, + encryptionSystem, + livekitRoom, + handRaised$, + reaction$, + ); } } @@ -498,8 +511,18 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel { participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, + handRaised$: Observable, + reaction$: Observable, ) { - super(id, member, participant$, encryptionSystem, livekitRoom); + super( + id, + member, + participant$, + encryptionSystem, + livekitRoom, + handRaised$, + reaction$, + ); // Sync the local volume with LiveKit combineLatest([ diff --git a/src/tile/GridTile.test.tsx b/src/tile/GridTile.test.tsx index d7edf3b3..16875c33 100644 --- a/src/tile/GridTile.test.tsx +++ b/src/tile/GridTile.test.tsx @@ -15,7 +15,8 @@ import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSess import { GridTile } from "./GridTile"; import { mockRtcMembership, withRemoteMedia } from "../utils/test"; import { GridTileViewModel } from "../state/TileViewModel"; -import { ReactionsProvider } from "../useReactions"; +import { ReactionsSenderProvider } from "../reactions/useReactionsSender"; +import type { CallViewModel } from "../state/CallViewModel"; global.IntersectionObserver = class MockIntersectionObserver { public observe(): void {} @@ -44,14 +45,19 @@ test("GridTile is accessible", async () => { off: () => {}, client: { getUserId: () => null, + getDeviceId: () => null, on: () => {}, off: () => {}, }, }, memberships: [], } as unknown as MatrixRTCSession; + const cVm = { + reactions$: of({}), + handsRaised$: of({}), + } as Partial as CallViewModel; const { container } = render( - + {}} @@ -59,7 +65,7 @@ test("GridTile is accessible", async () => { targetHeight={200} showSpeakingIndicators /> - , + , ); expect(await axe(container)).toHaveNoViolations(); // Name should be visible diff --git a/src/tile/GridTile.tsx b/src/tile/GridTile.tsx index 8c6b2d9b..9eb775d0 100644 --- a/src/tile/GridTile.tsx +++ b/src/tile/GridTile.tsx @@ -34,7 +34,7 @@ import { ToggleMenuItem, Menu, } from "@vector-im/compound-web"; -import { useObservableEagerState } from "observable-hooks"; +import { useObservableEagerState, useObservableState } from "observable-hooks"; import styles from "./GridTile.module.css"; import { @@ -48,8 +48,7 @@ import { MediaView } from "./MediaView"; import { useLatest } from "../useLatest"; import { type GridTileViewModel } from "../state/TileViewModel"; import { useMergedRefs } from "../useMergedRefs"; -import { useReactions } from "../useReactions"; -import { type ReactionOption } from "../reactions"; +import { useReactionsSender } from "../reactions/useReactionsSender"; interface TileProps { className?: string; @@ -82,6 +81,7 @@ const UserMediaTile = forwardRef( }, ref, ) => { + const { toggleRaisedHand } = useReactionsSender(); const { t } = useTranslation(); const video = useObservableEagerState(vm.video$); const unencryptedWarning = useObservableEagerState(vm.unencryptedWarning$); @@ -97,7 +97,8 @@ const UserMediaTile = forwardRef( }, [vm], ); - const { raisedHands, toggleRaisedHand, reactions } = useReactions(); + const handRaised = useObservableState(vm.handRaised$); + const reaction = useObservableState(vm.reaction$); const AudioIcon = locallyMuted ? VolumeOffSolidIcon @@ -124,9 +125,6 @@ const UserMediaTile = forwardRef( ); - const handRaised: Date | undefined = raisedHands[vm.member?.userId ?? ""]; - const currentReaction: ReactionOption | undefined = - reactions[vm.member?.userId ?? ""]; const raisedHandOnClick = vm.local ? (): void => void toggleRaisedHand() : undefined; @@ -144,7 +142,7 @@ const UserMediaTile = forwardRef( videoFit={cropVideo ? "cover" : "contain"} className={classNames(className, styles.tile, { [styles.speaking]: showSpeaking, - [styles.handRaised]: !showSpeaking && !!handRaised, + [styles.handRaised]: !showSpeaking && handRaised, })} nameTagLeadingIcon={ ( {menu} } - raisedHandTime={handRaised} - currentReaction={currentReaction} + raisedHandTime={handRaised ?? undefined} + currentReaction={reaction ?? undefined} raisedHandOnClick={raisedHandOnClick} localParticipant={vm.local} {...props} diff --git a/src/useReactions.test.tsx b/src/useReactions.test.tsx deleted file mode 100644 index 56edd7e1..00000000 --- a/src/useReactions.test.tsx +++ /dev/null @@ -1,173 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only -Please see LICENSE in the repository root for full details. -*/ - -import { render } from "@testing-library/react"; -import { act, type FC } from "react"; -import { describe, expect, test } from "vitest"; -import { RoomEvent } from "matrix-js-sdk/src/matrix"; - -import { useReactions } from "./useReactions"; -import { - createHandRaisedReaction, - createRedaction, - MockRoom, - MockRTCSession, - TestReactionsWrapper, -} from "./utils/testReactions"; - -const memberUserIdAlice = "@alice:example.org"; -const memberEventAlice = "$membership-alice:example.org"; -const memberUserIdBob = "@bob:example.org"; -const memberEventBob = "$membership-bob:example.org"; - -const membership: Record = { - [memberEventAlice]: memberUserIdAlice, - [memberEventBob]: memberUserIdBob, - "$membership-charlie:example.org": "@charlie:example.org", -}; - -/** - * Test explanation. - * This test suite checks that the useReactions hook appropriately reacts - * to new reactions, redactions and membership changesin the room. There is - * a large amount of test structure used to construct a mock environment. - */ - -const TestComponent: FC = () => { - const { raisedHands } = useReactions(); - return ( -
-
    - {Object.entries(raisedHands).map(([userId, date]) => ( -
  • - {userId} - -
  • - ))} -
-
- ); -}; - -describe("useReactions", () => { - test("starts with an empty list", () => { - const rtcSession = new MockRTCSession( - new MockRoom(memberUserIdAlice), - membership, - ); - const { queryByRole } = render( - - - , - ); - expect(queryByRole("list")?.children).to.have.lengthOf(0); - }); - test("handles incoming raised hand", async () => { - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { queryByRole } = render( - - - , - ); - await act(() => room.testSendHandRaise(memberEventAlice, membership)); - expect(queryByRole("list")?.children).to.have.lengthOf(1); - await act(() => room.testSendHandRaise(memberEventBob, membership)); - expect(queryByRole("list")?.children).to.have.lengthOf(2); - }); - test("handles incoming unraised hand", async () => { - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { queryByRole } = render( - - - , - ); - const reactionEventId = await act(() => - room.testSendHandRaise(memberEventAlice, membership), - ); - expect(queryByRole("list")?.children).to.have.lengthOf(1); - await act(() => - room.emit( - RoomEvent.Redaction, - createRedaction(memberUserIdAlice, reactionEventId), - room, - undefined, - ), - ); - expect(queryByRole("list")?.children).to.have.lengthOf(0); - }); - test("handles loading prior raised hand events", () => { - const room = new MockRoom(memberUserIdAlice, [ - createHandRaisedReaction(memberEventAlice, membership), - ]); - const rtcSession = new MockRTCSession(room, membership); - const { queryByRole } = render( - - - , - ); - expect(queryByRole("list")?.children).to.have.lengthOf(1); - }); - // If the membership event changes for a user, we want to remove - // the raised hand event. - test("will remove reaction when a member leaves the call", () => { - const room = new MockRoom(memberUserIdAlice, [ - createHandRaisedReaction(memberEventAlice, membership), - ]); - const rtcSession = new MockRTCSession(room, membership); - const { queryByRole } = render( - - - , - ); - expect(queryByRole("list")?.children).to.have.lengthOf(1); - act(() => rtcSession.testRemoveMember(memberUserIdAlice)); - expect(queryByRole("list")?.children).to.have.lengthOf(0); - }); - test("will remove reaction when a member joins via a new event", () => { - const room = new MockRoom(memberUserIdAlice, [ - createHandRaisedReaction(memberEventAlice, membership), - ]); - const rtcSession = new MockRTCSession(room, membership); - const { queryByRole } = render( - - - , - ); - expect(queryByRole("list")?.children).to.have.lengthOf(1); - // Simulate leaving and rejoining - act(() => { - rtcSession.testRemoveMember(memberUserIdAlice); - rtcSession.testAddMember(memberUserIdAlice); - }); - expect(queryByRole("list")?.children).to.have.lengthOf(0); - }); - test("ignores invalid sender for historic event", () => { - const room = new MockRoom(memberUserIdAlice, [ - createHandRaisedReaction(memberEventAlice, memberUserIdBob), - ]); - const rtcSession = new MockRTCSession(room, membership); - const { queryByRole } = render( - - - , - ); - expect(queryByRole("list")?.children).to.have.lengthOf(0); - }); - test("ignores invalid sender for new event", async () => { - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { queryByRole } = render( - - - , - ); - await act(() => room.testSendHandRaise(memberEventAlice, memberUserIdBob)); - expect(queryByRole("list")?.children).to.have.lengthOf(0); - }); -}); diff --git a/src/useReactions.tsx b/src/useReactions.tsx deleted file mode 100644 index 7289187a..00000000 --- a/src/useReactions.tsx +++ /dev/null @@ -1,405 +0,0 @@ -/* -Copyright 2024 Milton Moura - -SPDX-License-Identifier: AGPL-3.0-only -Please see LICENSE in the repository root for full details. -*/ - -import { - EventType, - type MatrixEvent, - RelationType, - RoomEvent as MatrixRoomEvent, - MatrixEventEvent, -} from "matrix-js-sdk/src/matrix"; -import { type ReactionEventContent } from "matrix-js-sdk/src/types"; -import { - createContext, - useContext, - useState, - type ReactNode, - useCallback, - useEffect, - useMemo, -} from "react"; -import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; -import { logger } from "matrix-js-sdk/src/logger"; - -import { useMatrixRTCSessionMemberships } from "./useMatrixRTCSessionMemberships"; -import { useClientState } from "./ClientContext"; -import { - type ECallReactionEventContent, - ElementCallReactionEventType, - GenericReaction, - type ReactionOption, - ReactionSet, -} from "./reactions"; -import { useLatest } from "./useLatest"; - -interface ReactionsContextType { - raisedHands: Record; - supportsReactions: boolean; - reactions: Record; - toggleRaisedHand: () => Promise; - sendReaction: (reaction: ReactionOption) => Promise; -} - -const ReactionsContext = createContext( - undefined, -); - -interface RaisedHandInfo { - /** - * Call membership event that was reacted to. - */ - membershipEventId: string; - /** - * Event ID of the reaction itself. - */ - reactionEventId: string; - /** - * The time when the reaction was raised. - */ - time: Date; -} - -const REACTION_ACTIVE_TIME_MS = 3000; - -export const useReactions = (): ReactionsContextType => { - const context = useContext(ReactionsContext); - if (!context) { - throw new Error("useReactions must be used within a ReactionsProvider"); - } - return context; -}; - -/** - * Provider that handles raised hand reactions for a given `rtcSession`. - */ -export const ReactionsProvider = ({ - children, - rtcSession, -}: { - children: ReactNode; - rtcSession: MatrixRTCSession; -}): JSX.Element => { - const [raisedHands, setRaisedHands] = useState< - Record - >({}); - const memberships = useMatrixRTCSessionMemberships(rtcSession); - const clientState = useClientState(); - const supportsReactions = - clientState?.state === "valid" && clientState.supportedFeatures.reactions; - const room = rtcSession.room; - const myUserId = room.client.getUserId(); - - const [reactions, setReactions] = useState>( - {}, - ); - - // Reduce the data down for the consumers. - const resultRaisedHands = useMemo( - () => - Object.fromEntries( - Object.entries(raisedHands).map(([uid, data]) => [uid, data.time]), - ), - [raisedHands], - ); - const addRaisedHand = useCallback((userId: string, info: RaisedHandInfo) => { - setRaisedHands((prevRaisedHands) => ({ - ...prevRaisedHands, - [userId]: info, - })); - }, []); - - const removeRaisedHand = useCallback((userId: string) => { - setRaisedHands( - ({ [userId]: _removed, ...remainingRaisedHands }) => remainingRaisedHands, - ); - }, []); - - // This effect will check the state whenever the membership of the session changes. - useEffect(() => { - // Fetches the first reaction for a given event. - const getLastReactionEvent = ( - eventId: string, - expectedSender: string, - ): MatrixEvent | undefined => { - const relations = room.relations.getChildEventsForEvent( - eventId, - RelationType.Annotation, - EventType.Reaction, - ); - const allEvents = relations?.getRelations() ?? []; - return allEvents.find( - (reaction) => - reaction.event.sender === expectedSender && - reaction.getType() === EventType.Reaction && - reaction.getContent()?.["m.relates_to"]?.key === "🖐️", - ); - }; - - // Remove any raised hands for users no longer joined to the call. - for (const userId of Object.keys(raisedHands).filter( - (rhId) => !memberships.find((u) => u.sender == rhId), - )) { - removeRaisedHand(userId); - } - - // For each member in the call, check to see if a reaction has - // been raised and adjust. - for (const m of memberships) { - if (!m.sender || !m.eventId) { - continue; - } - if ( - raisedHands[m.sender] && - raisedHands[m.sender].membershipEventId !== m.eventId - ) { - // Membership event for sender has changed since the hand - // was raised, reset. - removeRaisedHand(m.sender); - } - const reaction = getLastReactionEvent(m.eventId, m.sender); - if (reaction) { - const eventId = reaction?.getId(); - if (!eventId) { - continue; - } - addRaisedHand(m.sender, { - membershipEventId: m.eventId, - reactionEventId: eventId, - time: new Date(reaction.localTimestamp), - }); - } - } - // Ignoring raisedHands here because we don't want to trigger each time the raised - // hands set is updated. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [room, memberships, myUserId, addRaisedHand, removeRaisedHand]); - - const latestMemberships = useLatest(memberships); - const latestRaisedHands = useLatest(raisedHands); - - const myMembership = useMemo( - () => memberships.find((m) => m.sender === myUserId)?.eventId, - [memberships, myUserId], - ); - - // This effect handles any *live* reaction/redactions in the room. - useEffect(() => { - const reactionTimeouts = new Set(); - const handleReactionEvent = (event: MatrixEvent): void => { - // Decrypted events might come from a different room - if (event.getRoomId() !== room.roomId) return; - // Skip any events that are still sending. - if (event.isSending()) return; - - const sender = event.getSender(); - const reactionEventId = event.getId(); - // Skip any event without a sender or event ID. - if (!sender || !reactionEventId) return; - - room.client - .decryptEventIfNeeded(event) - .catch((e) => logger.warn(`Failed to decrypt ${event.getId()}`, e)); - if (event.isBeingDecrypted() || event.isDecryptionFailure()) return; - - if (event.getType() === ElementCallReactionEventType) { - const content: ECallReactionEventContent = event.getContent(); - - const membershipEventId = content?.["m.relates_to"]?.event_id; - // Check to see if this reaction was made to a membership event (and the - // sender of the reaction matches the membership) - if ( - !latestMemberships.current.some( - (e) => e.eventId === membershipEventId && e.sender === sender, - ) - ) { - logger.warn( - `Reaction target was not a membership event for ${sender}, ignoring`, - ); - return; - } - - if (!content.emoji) { - logger.warn(`Reaction had no emoji from ${reactionEventId}`); - return; - } - - const segment = new Intl.Segmenter(undefined, { - granularity: "grapheme", - }) - .segment(content.emoji) - [Symbol.iterator](); - const emoji = segment.next().value?.segment; - - if (!emoji) { - logger.warn( - `Reaction had no emoji from ${reactionEventId} after splitting`, - ); - return; - } - - // One of our custom reactions - const reaction = { - ...GenericReaction, - emoji, - // If we don't find a reaction, we can fallback to the generic sound. - ...ReactionSet.find((r) => r.name === content.name), - }; - - setReactions((reactions) => { - if (reactions[sender]) { - // We've still got a reaction from this user, ignore it to prevent spamming - return reactions; - } - const timeout = window.setTimeout(() => { - // Clear the reaction after some time. - setReactions(({ [sender]: _unused, ...remaining }) => remaining); - reactionTimeouts.delete(timeout); - }, REACTION_ACTIVE_TIME_MS); - reactionTimeouts.add(timeout); - return { - ...reactions, - [sender]: reaction, - }; - }); - } else if (event.getType() === EventType.Reaction) { - const content = event.getContent() as ReactionEventContent; - const membershipEventId = content["m.relates_to"].event_id; - - // Check to see if this reaction was made to a membership event (and the - // sender of the reaction matches the membership) - if ( - !latestMemberships.current.some( - (e) => e.eventId === membershipEventId && e.sender === sender, - ) - ) { - logger.warn( - `Reaction target was not a membership event for ${sender}, ignoring`, - ); - return; - } - - if (content?.["m.relates_to"].key === "🖐️") { - addRaisedHand(sender, { - reactionEventId, - membershipEventId, - time: new Date(event.localTimestamp), - }); - } - } else if (event.getType() === EventType.RoomRedaction) { - const targetEvent = event.event.redacts; - const targetUser = Object.entries(latestRaisedHands.current).find( - ([_u, r]) => r.reactionEventId === targetEvent, - )?.[0]; - if (!targetUser) { - // Reaction target was not for us, ignoring - return; - } - removeRaisedHand(targetUser); - } - }; - - room.on(MatrixRoomEvent.Timeline, handleReactionEvent); - room.on(MatrixRoomEvent.Redaction, handleReactionEvent); - room.client.on(MatrixEventEvent.Decrypted, handleReactionEvent); - - // We listen for a local echo to get the real event ID, as timeline events - // may still be sending. - room.on(MatrixRoomEvent.LocalEchoUpdated, handleReactionEvent); - - return (): void => { - room.off(MatrixRoomEvent.Timeline, handleReactionEvent); - room.off(MatrixRoomEvent.Redaction, handleReactionEvent); - room.client.off(MatrixEventEvent.Decrypted, handleReactionEvent); - room.off(MatrixRoomEvent.LocalEchoUpdated, handleReactionEvent); - reactionTimeouts.forEach((t) => clearTimeout(t)); - // If we're clearing timeouts, we also clear all reactions. - setReactions({}); - }; - }, [ - room, - addRaisedHand, - removeRaisedHand, - latestMemberships, - latestRaisedHands, - ]); - - const toggleRaisedHand = useCallback(async () => { - if (!myUserId) { - return; - } - const myReactionId = raisedHands[myUserId]?.reactionEventId; - - if (!myReactionId) { - try { - if (!myMembership) { - throw new Error("Cannot find own membership event"); - } - const reaction = await room.client.sendEvent( - rtcSession.room.roomId, - EventType.Reaction, - { - "m.relates_to": { - rel_type: RelationType.Annotation, - event_id: myMembership, - key: "🖐️", - }, - }, - ); - logger.debug("Sent raise hand event", reaction.event_id); - } catch (ex) { - logger.error("Failed to send raised hand", ex); - } - } else { - try { - await room.client.redactEvent(rtcSession.room.roomId, myReactionId); - logger.debug("Redacted raise hand event"); - } catch (ex) { - logger.error("Failed to redact reaction event", myReactionId, ex); - throw ex; - } - } - }, [myMembership, myUserId, raisedHands, rtcSession, room]); - - const sendReaction = useCallback( - async (reaction: ReactionOption) => { - if (!myUserId || reactions[myUserId]) { - // We're still reacting - return; - } - if (!myMembership) { - throw new Error("Cannot find own membership event"); - } - await room.client.sendEvent( - rtcSession.room.roomId, - ElementCallReactionEventType, - { - "m.relates_to": { - rel_type: RelationType.Reference, - event_id: myMembership, - }, - emoji: reaction.emoji, - name: reaction.name, - }, - ); - }, - [myMembership, reactions, room, myUserId, rtcSession], - ); - - return ( - - {children} - - ); -}; diff --git a/src/utils/test-fixtures.ts b/src/utils/test-fixtures.ts new file mode 100644 index 00000000..a105b5f7 --- /dev/null +++ b/src/utils/test-fixtures.ts @@ -0,0 +1,24 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { + mockRtcMembership, + mockMatrixRoomMember, + mockRemoteParticipant, + mockLocalParticipant, +} from "./test"; + +export const aliceRtcMember = mockRtcMembership("@alice:example.org", "AAAA"); +export const alice = mockMatrixRoomMember(aliceRtcMember); +export const aliceId = `${alice.userId}:${aliceRtcMember.deviceId}`; +export const aliceParticipant = mockRemoteParticipant({ identity: aliceId }); + +export const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); +export const local = mockMatrixRoomMember(localRtcMember); +export const localParticipant = mockLocalParticipant({ identity: "" }); + +export const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB"); diff --git a/src/utils/test-viewmodel.ts b/src/utils/test-viewmodel.ts new file mode 100644 index 00000000..799ea1a1 --- /dev/null +++ b/src/utils/test-viewmodel.ts @@ -0,0 +1,150 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { ConnectionState } from "livekit-client"; +import { type MatrixClient } from "matrix-js-sdk/src/client"; +import { type RoomMember } from "matrix-js-sdk/src/matrix"; +import { + type CallMembership, + type MatrixRTCSession, +} from "matrix-js-sdk/src/matrixrtc"; +import { BehaviorSubject, of } from "rxjs"; +import { vitest } from "vitest"; +import { type RelationsContainer } from "matrix-js-sdk/src/models/relations-container"; +import EventEmitter from "events"; + +import { E2eeType } from "../e2ee/e2eeType"; +import { CallViewModel } from "../state/CallViewModel"; +import { mockLivekitRoom, mockMatrixRoom, MockRTCSession } from "./test"; +import { + aliceRtcMember, + aliceParticipant, + localParticipant, + localRtcMember, +} from "./test-fixtures"; +import { type RaisedHandInfo, type ReactionInfo } from "../reactions"; + +export function getBasicRTCSession( + members: RoomMember[], + initialRemoteRtcMemberships: CallMembership[] = [aliceRtcMember], +): { + rtcSession: MockRTCSession; + remoteRtcMemberships$: BehaviorSubject; +} { + const matrixRoomId = "!myRoomId:example.com"; + const matrixRoomMembers = new Map(members.map((p) => [p.userId, p])); + + const roomEmitter = new EventEmitter(); + const clientEmitter = new EventEmitter(); + const matrixRoom = mockMatrixRoom({ + relations: { + getChildEventsForEvent: vitest.fn(), + } as Partial as RelationsContainer, + client: { + getUserId: () => localRtcMember.sender, + getDeviceId: () => localRtcMember.deviceId, + sendEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }), + redactEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }), + decryptEventIfNeeded: vitest.fn().mockResolvedValue(undefined), + on: vitest + .fn() + .mockImplementation( + (eventName: string, fn: (...args: unknown[]) => void) => { + clientEmitter.on(eventName, fn); + }, + ), + emit: (eventName: string, ...args: unknown[]) => + clientEmitter.emit(eventName, ...args), + off: vitest + .fn() + .mockImplementation( + (eventName: string, fn: (...args: unknown[]) => void) => { + clientEmitter.off(eventName, fn); + }, + ), + } as Partial as MatrixClient, + getMember: (userId) => matrixRoomMembers.get(userId) ?? null, + roomId: matrixRoomId, + on: vitest + .fn() + .mockImplementation( + (eventName: string, fn: (...args: unknown[]) => void) => { + roomEmitter.on(eventName, fn); + }, + ), + emit: (eventName: string, ...args: unknown[]) => + roomEmitter.emit(eventName, ...args), + off: vitest + .fn() + .mockImplementation( + (eventName: string, fn: (...args: unknown[]) => void) => { + roomEmitter.off(eventName, fn); + }, + ), + }); + + const remoteRtcMemberships$ = new BehaviorSubject( + initialRemoteRtcMemberships, + ); + + const rtcSession = new MockRTCSession( + matrixRoom, + localRtcMember, + ).withMemberships(remoteRtcMemberships$); + + return { + rtcSession, + remoteRtcMemberships$, + }; +} + +/** + * Construct a basic CallViewModel to test components that make use of it. + * @param members + * @param initialRemoteRtcMemberships + * @returns + */ +export function getBasicCallViewModelEnvironment( + members: RoomMember[], + initialRemoteRtcMemberships: CallMembership[] = [aliceRtcMember], +): { + vm: CallViewModel; + remoteRtcMemberships$: BehaviorSubject; + rtcSession: MockRTCSession; + handRaisedSubject$: BehaviorSubject>; + reactionsSubject$: BehaviorSubject>; +} { + const { rtcSession, remoteRtcMemberships$ } = getBasicRTCSession( + members, + initialRemoteRtcMemberships, + ); + const handRaisedSubject$ = new BehaviorSubject({}); + const reactionsSubject$ = new BehaviorSubject({}); + + const remoteParticipants$ = of([aliceParticipant]); + const liveKitRoom = mockLivekitRoom( + { localParticipant }, + { remoteParticipants$ }, + ); + const vm = new CallViewModel( + rtcSession as unknown as MatrixRTCSession, + liveKitRoom, + { + kind: E2eeType.PER_PARTICIPANT, + }, + of(ConnectionState.Connected), + handRaisedSubject$, + reactionsSubject$, + ); + return { + vm, + remoteRtcMemberships$, + rtcSession, + handRaisedSubject$: handRaisedSubject$, + reactionsSubject$: reactionsSubject$, + }; +} diff --git a/src/utils/test.ts b/src/utils/test.ts index db0d8959..41e85ba3 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -28,6 +28,7 @@ import { type RemoteTrackPublication, type Room as LivekitRoom, } from "livekit-client"; +import { randomUUID } from "crypto"; import { LocalUserMediaViewModel, @@ -132,6 +133,7 @@ export function mockRtcMembership( }; const event = new MatrixEvent({ sender: typeof user === "string" ? user : user.userId, + event_id: `$-ev-${randomUUID()}:example.org`, }); return new CallMembership(event, data); } @@ -203,6 +205,8 @@ export async function withLocalMedia( kind: E2eeType.PER_PARTICIPANT, }, mockLivekitRoom({ localParticipant }), + of(null), + of(null), ); try { await continuation(vm); @@ -239,6 +243,8 @@ export async function withRemoteMedia( kind: E2eeType.PER_PARTICIPANT, }, mockLivekitRoom({}, { remoteParticipants$: of([remoteParticipant]) }), + of(null), + of(null), ); try { await continuation(vm); diff --git a/src/utils/testReactions.tsx b/src/utils/testReactions.tsx deleted file mode 100644 index 6fad030c..00000000 --- a/src/utils/testReactions.tsx +++ /dev/null @@ -1,214 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only -Please see LICENSE in the repository root for full details. -*/ - -import { type PropsWithChildren, type ReactNode } from "react"; -import { randomUUID } from "crypto"; -import EventEmitter from "events"; -import { type MatrixClient } from "matrix-js-sdk/src/client"; -import { EventType, RoomEvent, RelationType } from "matrix-js-sdk/src/matrix"; -import { - MatrixEvent, - EventTimeline, - EventTimelineSet, - type Room, -} from "matrix-js-sdk/src/matrix"; -import { - type MatrixRTCSession, - MatrixRTCSessionEvent, -} from "matrix-js-sdk/src/matrixrtc"; - -import { ReactionsProvider } from "../useReactions"; -import { - type ECallReactionEventContent, - ElementCallReactionEventType, - type ReactionOption, -} from "../reactions"; - -export const TestReactionsWrapper = ({ - rtcSession, - children, -}: PropsWithChildren<{ - rtcSession: MockRTCSession | MatrixRTCSession; -}>): ReactNode => { - return ( - - {children} - - ); -}; - -export class MockRTCSession extends EventEmitter { - public memberships: { - sender: string; - eventId: string; - createdTs: () => Date; - }[]; - - public constructor( - public readonly room: MockRoom, - membership: Record, - ) { - super(); - this.memberships = Object.entries(membership).map(([eventId, sender]) => ({ - sender, - eventId, - createdTs: (): Date => new Date(), - })); - } - - public testRemoveMember(userId: string): void { - this.memberships = this.memberships.filter((u) => u.sender !== userId); - this.emit(MatrixRTCSessionEvent.MembershipsChanged); - } - - public testAddMember(sender: string): void { - this.memberships.push({ - sender, - eventId: `!fake-${randomUUID()}:event`, - createdTs: (): Date => new Date(), - }); - this.emit(MatrixRTCSessionEvent.MembershipsChanged); - } -} - -export function createHandRaisedReaction( - parentMemberEvent: string, - membershipOrOverridenSender: Record | string, -): MatrixEvent { - return new MatrixEvent({ - sender: - typeof membershipOrOverridenSender === "string" - ? membershipOrOverridenSender - : membershipOrOverridenSender[parentMemberEvent], - type: EventType.Reaction, - origin_server_ts: new Date().getTime(), - content: { - "m.relates_to": { - key: "🖐️", - event_id: parentMemberEvent, - }, - }, - event_id: randomUUID(), - }); -} - -export function createRedaction( - sender: string, - reactionEventId: string, -): MatrixEvent { - return new MatrixEvent({ - sender, - type: EventType.RoomRedaction, - origin_server_ts: new Date().getTime(), - redacts: reactionEventId, - content: {}, - event_id: randomUUID(), - }); -} - -export class MockRoom extends EventEmitter { - public readonly testSentEvents: Parameters[] = []; - public readonly testRedactedEvents: Parameters< - MatrixClient["redactEvent"] - >[] = []; - - public constructor( - private readonly ownUserId: string, - private readonly existingRelations: MatrixEvent[] = [], - ) { - super(); - } - - public get client(): MatrixClient { - return { - getUserId: (): string => this.ownUserId, - sendEvent: async ( - ...props: Parameters - ): ReturnType => { - this.testSentEvents.push(props); - return Promise.resolve({ event_id: randomUUID() }); - }, - redactEvent: async ( - ...props: Parameters - ): ReturnType => { - this.testRedactedEvents.push(props); - return Promise.resolve({ event_id: randomUUID() }); - }, - decryptEventIfNeeded: async () => {}, - on() { - return this; - }, - off() { - return this; - }, - } as unknown as MatrixClient; - } - - public get relations(): Room["relations"] { - return { - getChildEventsForEvent: (membershipEventId: string) => ({ - getRelations: (): MatrixEvent[] => { - return this.existingRelations.filter( - (r) => - r.getContent()["m.relates_to"]?.event_id === membershipEventId, - ); - }, - }), - } as unknown as Room["relations"]; - } - - public testSendHandRaise( - parentMemberEvent: string, - membershipOrOverridenSender: Record | string, - ): string { - const evt = createHandRaisedReaction( - parentMemberEvent, - membershipOrOverridenSender, - ); - this.emit(RoomEvent.Timeline, evt, this, undefined, false, { - timeline: new EventTimeline(new EventTimelineSet(undefined)), - }); - return evt.getId()!; - } - - public testSendReaction( - parentMemberEvent: string, - reaction: ReactionOption, - membershipOrOverridenSender: Record | string, - ): string { - const evt = new MatrixEvent({ - sender: - typeof membershipOrOverridenSender === "string" - ? membershipOrOverridenSender - : membershipOrOverridenSender[parentMemberEvent], - type: ElementCallReactionEventType, - origin_server_ts: new Date().getTime(), - content: { - "m.relates_to": { - rel_type: RelationType.Reference, - event_id: parentMemberEvent, - }, - emoji: reaction.emoji, - name: reaction.name, - } satisfies ECallReactionEventContent, - event_id: randomUUID(), - }); - - this.emit(RoomEvent.Timeline, evt, this, undefined, false, { - timeline: new EventTimeline(new EventTimelineSet(undefined)), - }); - return evt.getId()!; - } - - public getMember(): void { - return; - } - - public testGetAsMatrixRoom(): Room { - return this as unknown as Room; - } -} diff --git a/vitest.config.js b/vitest.config.js index 098a0b0c..68fef5be 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -16,7 +16,12 @@ export default defineConfig((configEnv) => coverage: { reporter: ["html", "json"], include: ["src/"], - exclude: ["src/**/*.{d,test}.{ts,tsx}", "src/utils/test.ts"], + exclude: [ + "src/**/*.{d,test}.{ts,tsx}", + "src/utils/test.ts", + "src/utils/test-viewmodel.ts", + "src/utils/test-fixtures.ts", + ], }, }, }), From 6776c2af351f4f8cf4cb3e9c8539f8ad7ba1724d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:00:32 +0100 Subject: [PATCH 67/72] Update Compound (#2895) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 279 ++++++++++++++++++++++-------------------------------- 1 file changed, 113 insertions(+), 166 deletions(-) diff --git a/yarn.lock b/yarn.lock index d3b8da40..419dc141 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1790,10 +1790,10 @@ dependencies: "@floating-ui/dom" "^1.0.0" -"@floating-ui/react@^0.26.24": - version "0.26.28" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7" - integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw== +"@floating-ui/react@^0.27.0": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.2.tgz#901a04e93061c427d45b69a29c99f641a8b3a7bc" + integrity sha512-k/yP6a9K9QwhLfIu87iUZxCH6XN5z5j/VUHHq0dEnbZYY2Y9jz68E/LXFtK8dkiaYltS2WYohnyKC0VcwVneVg== dependencies: "@floating-ui/react-dom" "^2.1.2" "@floating-ui/utils" "^0.2.8" @@ -2335,32 +2335,17 @@ resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46" integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ== -"@radix-ui/primitive@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" - integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA== - "@radix-ui/primitive@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.1.tgz#fc169732d755c7fbad33ba8d0cd7fd10c90dc8e3" integrity sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA== -"@radix-ui/react-arrow@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz#744f388182d360b86285217e43b6c63633f39e7a" - integrity sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw== +"@radix-ui/react-arrow@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz#2103721933a8bfc6e53bbfbdc1aaad5fc8ba0dd7" + integrity sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w== dependencies: - "@radix-ui/react-primitive" "2.0.0" - -"@radix-ui/react-collection@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" - integrity sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw== - dependencies: - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-context" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" - "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-collection@1.1.1": version "1.1.1" @@ -2372,33 +2357,23 @@ "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-slot" "1.1.1" -"@radix-ui/react-compose-refs@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74" - integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw== - "@radix-ui/react-compose-refs@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz#6f766faa975f8738269ebb8a23bad4f5a8d2faec" integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw== "@radix-ui/react-context-menu@^2.2.1": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.2.tgz#efcddc559fc3011721b65148f062d04027f76c7a" - integrity sha512-99EatSTpW+hRYHt7m8wdDlLtkmTovEe8Z/hnxUPV+SKuuNL5HWNhQI4QSdjZqNSgXHay2z4M3Dym73j9p2Gx5Q== + version "2.2.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.4.tgz#8fe70ffdfec25ada35a60d100496cace1b75a977" + integrity sha512-ap4wdGwK52rJxGkwukU1NrnEodsUFQIooANKu+ey7d6raQ2biTcEf8za1zr0mgFHieevRTB2nK4dJeN8pTAZGQ== dependencies: - "@radix-ui/primitive" "1.1.0" + "@radix-ui/primitive" "1.1.1" "@radix-ui/react-context" "1.1.1" - "@radix-ui/react-menu" "2.1.2" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-menu" "2.1.4" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0" -"@radix-ui/react-context@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" - integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== - "@radix-ui/react-context@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a" @@ -2429,17 +2404,6 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc" integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg== -"@radix-ui/react-dismissable-layer@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz#cbdcb739c5403382bdde5f9243042ba643883396" - integrity sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ== - dependencies: - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" - "@radix-ui/react-use-callback-ref" "1.1.0" - "@radix-ui/react-use-escape-keydown" "1.1.0" - "@radix-ui/react-dismissable-layer@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.2.tgz#771594b202f32bc8ffeb278c565f10c513814aee" @@ -2451,17 +2415,28 @@ "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-escape-keydown" "1.1.0" -"@radix-ui/react-dropdown-menu@^2.1.1": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz#acc49577130e3c875ef0133bd1e271ea3392d924" - integrity sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA== +"@radix-ui/react-dismissable-layer@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz#4ee0f0f82d53bf5bd9db21665799bb0d1bad5ed8" + integrity sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg== dependencies: - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-escape-keydown" "1.1.0" + +"@radix-ui/react-dropdown-menu@^2.1.1": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.4.tgz#5e1f453296dd9ae99224a26c36851832d26cf507" + integrity sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA== + dependencies: + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-context" "1.1.1" "@radix-ui/react-id" "1.1.0" - "@radix-ui/react-menu" "2.1.2" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-menu" "2.1.4" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-focus-guards@1.1.1": @@ -2469,15 +2444,6 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz#8635edd346304f8b42cae86b05912b61aef27afe" integrity sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg== -"@radix-ui/react-focus-scope@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz#ebe2891a298e0a33ad34daab2aad8dea31caf0b2" - integrity sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA== - dependencies: - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" - "@radix-ui/react-use-callback-ref" "1.1.0" - "@radix-ui/react-focus-scope@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz#5c602115d1db1c4fcfa0fae4c3b09bb8919853cb" @@ -2488,16 +2454,16 @@ "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-form@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-form/-/react-form-0.1.0.tgz#7111a6aa54a2bde0d11fb72643f9ffc871ac58ad" - integrity sha512-1/oVYPDjbFILOLIarcGcMKo+y6SbTVT/iUKVEw59CF4offwZgBgC3ZOeSBewjqU0vdA6FWTPWTN63obj55S/tQ== + version "0.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-form/-/react-form-0.1.1.tgz#eb9241a02f8d43f3a7e9cb448ab99a5926a29690" + integrity sha512-Ah2TBvzl2trb4DL9DQtyUJgAUfq/djMN7j5CHzdpbdR3W7OL8N4JcJgE80cXMf3ssCE+8yg0zFQoJ0srxqfsFA== dependencies: - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-context" "1.1.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" "@radix-ui/react-id" "1.1.0" - "@radix-ui/react-label" "2.1.0" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-label" "2.1.1" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-id@1.1.0": version "1.1.0" @@ -2506,61 +2472,53 @@ dependencies: "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-label@2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.1.0.tgz#3aa2418d70bb242be37c51ff5e51a2adcbc372e3" - integrity sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw== +"@radix-ui/react-label@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.1.1.tgz#f30bd577b26873c638006e4f65761d4c6b80566d" + integrity sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw== dependencies: - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-primitive" "2.0.1" -"@radix-ui/react-menu@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.2.tgz#91f6815845a4298dde775563ed2d80b7ad667899" - integrity sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg== +"@radix-ui/react-menu@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.4.tgz#ac7aec296f29608206a7c6ef6335d8f102edaa95" + integrity sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A== dependencies: - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-collection" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-collection" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-context" "1.1.1" "@radix-ui/react-direction" "1.1.0" - "@radix-ui/react-dismissable-layer" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.3" "@radix-ui/react-focus-guards" "1.1.1" - "@radix-ui/react-focus-scope" "1.1.0" + "@radix-ui/react-focus-scope" "1.1.1" "@radix-ui/react-id" "1.1.0" - "@radix-ui/react-popper" "1.2.0" - "@radix-ui/react-portal" "1.1.2" - "@radix-ui/react-presence" "1.1.1" - "@radix-ui/react-primitive" "2.0.0" - "@radix-ui/react-roving-focus" "1.1.0" - "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-popper" "1.2.1" + "@radix-ui/react-portal" "1.1.3" + "@radix-ui/react-presence" "1.1.2" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-roving-focus" "1.1.1" + "@radix-ui/react-slot" "1.1.1" "@radix-ui/react-use-callback-ref" "1.1.0" aria-hidden "^1.1.1" - react-remove-scroll "2.6.0" + react-remove-scroll "^2.6.1" -"@radix-ui/react-popper@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz#a3e500193d144fe2d8f5d5e60e393d64111f2a7a" - integrity sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg== +"@radix-ui/react-popper@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.1.tgz#2fc66cfc34f95f00d858924e3bee54beae2dff0a" + integrity sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw== dependencies: "@floating-ui/react-dom" "^2.0.0" - "@radix-ui/react-arrow" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-context" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-arrow" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-use-rect" "1.1.0" "@radix-ui/react-use-size" "1.1.0" "@radix-ui/rect" "1.1.0" -"@radix-ui/react-portal@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz#51eb46dae7505074b306ebcb985bf65cc547d74e" - integrity sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg== - dependencies: - "@radix-ui/react-primitive" "2.0.0" - "@radix-ui/react-use-layout-effect" "1.1.0" - "@radix-ui/react-portal@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.3.tgz#b0ea5141103a1671b715481b13440763d2ac4440" @@ -2569,14 +2527,6 @@ "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-presence@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1" - integrity sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A== - dependencies: - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-use-layout-effect" "1.1.0" - "@radix-ui/react-presence@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.2.tgz#bb764ed8a9118b7ec4512da5ece306ded8703cdc" @@ -2585,13 +2535,6 @@ "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-primitive@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" - integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw== - dependencies: - "@radix-ui/react-slot" "1.1.0" - "@radix-ui/react-primitive@2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz#6d9efc550f7520135366f333d1e820cf225fad9e" @@ -2600,34 +2543,34 @@ "@radix-ui/react-slot" "1.1.1" "@radix-ui/react-progress@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.1.0.tgz#28c267885ec154fc557ec7a66cb462787312f7e2" - integrity sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.1.1.tgz#af923714ba3723be9c510536749d6c530d8670e4" + integrity sha512-6diOawA84f/eMxFHcWut0aE1C2kyE9dOyCTQOMRR2C/qPiXz/X0SaiA/RLbapQaXUCmy0/hLMf9meSccD1N0pA== dependencies: - "@radix-ui/react-context" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" -"@radix-ui/react-roving-focus@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e" - integrity sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA== +"@radix-ui/react-roving-focus@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz#3b3abb1e03646937f28d9ab25e96343667ca6520" + integrity sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw== dependencies: - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-collection" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-context" "1.1.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-collection" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" "@radix-ui/react-direction" "1.1.0" "@radix-ui/react-id" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-separator@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e" - integrity sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.1.tgz#dd60621553c858238d876be9b0702287424866d2" + integrity sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw== dependencies: - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-slider@^1.1.2": version "1.2.2" @@ -2646,14 +2589,7 @@ "@radix-ui/react-use-previous" "1.1.0" "@radix-ui/react-use-size" "1.1.0" -"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84" - integrity sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw== - dependencies: - "@radix-ui/react-compose-refs" "1.1.0" - -"@radix-ui/react-slot@1.1.1": +"@radix-ui/react-slot@1.1.1", "@radix-ui/react-slot@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.1.tgz#ab9a0ffae4027db7dc2af503c223c978706affc3" integrity sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g== @@ -3602,16 +3538,16 @@ "@use-gesture/core" "10.3.1" "@vector-im/compound-design-tokens@^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.1.tgz#d6175a99fe4b97688464126f255386990f3048d6" - integrity sha512-QnUi2K14D9KTXxcLQKUU3V75cforZLMwhaaJDNftT8F5mG86950hAM+qhgDNEpEU+pkTffQj0/g/5859YmqWzQ== + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.3.tgz#8205ffb455a09d71a02d838f3dbb8503c4e6ec27" + integrity sha512-U4UF7MVguENf0lQnkU2a9p/3llTsLXzbzmFFOxi0h6ny2igNxZj/kROP/jXTxxV9xD4TNn3z098Bos4J/qJpBA== "@vector-im/compound-web@^7.2.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.4.0.tgz#a5af8af6346f8ff6c14c70f5d4eb2eab7357a7cc" - integrity sha512-ZRBUeEGNmj/fTkIRa8zGnyVN7ytowpfOtHChqNm+m/+OTJN3o/lOMuQHDV8jeSEW2YwPJqGvPuG/dRr89IcQkA== + version "7.6.1" + resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.6.1.tgz#c41fc8b2e4c5938041e1f0ff9792f8fbadd9ab87" + integrity sha512-LdHGFslkyky2aNPZwIOY9GgWn1VOUa2EBKHln8HBvpxnYPcs3/A2nb1+6SsJ7+Y0TzKc2HA0rZ3qPDhQ3hjZYQ== dependencies: - "@floating-ui/react" "^0.26.24" + "@floating-ui/react" "^0.27.0" "@radix-ui/react-context-menu" "^2.2.1" "@radix-ui/react-dropdown-menu" "^2.1.1" "@radix-ui/react-form" "^0.1.0" @@ -7481,7 +7417,7 @@ react-refresh@^0.14.2: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== -react-remove-scroll-bar@^2.3.6: +react-remove-scroll-bar@^2.3.6, react-remove-scroll-bar@^2.3.7: version "2.3.8" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== @@ -7500,6 +7436,17 @@ react-remove-scroll@2.6.0: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" +react-remove-scroll@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz#2518d2c5112e71ea8928f1082a58459b5c7a2a97" + integrity sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw== + dependencies: + react-remove-scroll-bar "^2.3.7" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.3" + use-sidecar "^1.1.2" + react-router-dom@^5.2.0: version "5.3.4" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" @@ -8719,10 +8666,10 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -use-callback-ref@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.2.tgz#6134c7f6ff76e2be0b56c809b17a650c942b1693" - integrity sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA== +use-callback-ref@^1.3.0, use-callback-ref@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" + integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== dependencies: tslib "^2.0.0" From dc904fdbd2f2fc4ccd94194e3a3998e2eebb075b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:01:44 +0100 Subject: [PATCH 68/72] Update dependency @livekit/components-react to v2.6.10 (#2920) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 419dc141..9e932ef7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1941,9 +1941,9 @@ rxjs "7.8.1" "@livekit/components-react@^2.0.0": - version "2.6.9" - resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.6.9.tgz#2ff4691dc2cae6ed4c4b2e586a255d00e494bf9c" - integrity sha512-j43i/Dm8dlI2jxv5wv0s+69QPVqVEjg0y2tyznfs/7RDcaIZsIIzNijPu1kLditerzvzQdRsOgFQ3UWONcTkGA== + version "2.6.10" + resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.6.10.tgz#949d6e65e8507e2d8a4c75bf190adf56f6358175" + integrity sha512-aR8rqCIEvT3QYHuVEm67THRmNd9x25FTmU3Phi928FhzQJXDBO0N1/5d6qEE/wuDOgXMOoBgA98qsdYDIi2f+g== dependencies: "@livekit/components-core" "0.11.10" clsx "2.1.1" From 89e33743ae923c2d6e5c07693b866774801c2a10 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 02:39:08 +0000 Subject: [PATCH 69/72] Update LiveKit components --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9e932ef7..120058a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1931,21 +1931,21 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@livekit/components-core@0.11.10", "@livekit/components-core@^0.11.0": - version "0.11.10" - resolved "https://registry.yarnpkg.com/@livekit/components-core/-/components-core-0.11.10.tgz#136de5f7df487c984b95cd23d15d271708b30de8" - integrity sha512-PvFlKq1W64b9GfFjG7L4/o7ulAl5yFFpDTvG+JHQiXkaPaecMPt/qPbs6zdvUlC7om1TGMuW/pIN7o585Xz9Fg== +"@livekit/components-core@0.11.11", "@livekit/components-core@^0.11.0": + version "0.11.11" + resolved "https://registry.yarnpkg.com/@livekit/components-core/-/components-core-0.11.11.tgz#010040c05e95b8fe2485f90bb6773031b94c95a7" + integrity sha512-r6VgeS7zq9lMKiNJHSKUHMbmuirksEvHXEGsUvecsHTkZD9nhbhCB/01qCVaCVHVnLXvAYycNXVD4qcepPOpFQ== dependencies: "@floating-ui/dom" "1.6.11" loglevel "1.9.1" rxjs "7.8.1" "@livekit/components-react@^2.0.0": - version "2.6.10" - resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.6.10.tgz#949d6e65e8507e2d8a4c75bf190adf56f6358175" - integrity sha512-aR8rqCIEvT3QYHuVEm67THRmNd9x25FTmU3Phi928FhzQJXDBO0N1/5d6qEE/wuDOgXMOoBgA98qsdYDIi2f+g== + version "2.6.11" + resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.6.11.tgz#b477ac8d5a2cb97cc27d4fe82f9e2d60522a9e52" + integrity sha512-33V5D2yyt4p9qqjHSMFeu0mUl0S0Qd3zl+x4in9jNl3ZMZBwds9aFx+BtgAGymlAbjbxWPWmHn/SDI3R5/hJRQ== dependencies: - "@livekit/components-core" "0.11.10" + "@livekit/components-core" "0.11.11" clsx "2.1.1" usehooks-ts "3.1.0" From 2c33d6582445c96391b9fb71bc5451a895bb7f2e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 6 Jan 2025 09:47:39 +0000 Subject: [PATCH 70/72] Add developer mode option to show RTC connection statistics (#2904) * Add developer mode option to show RTC connection statistics * Add note about localization * Add titles to help explain what the numbers are * Workaround horizontal scrolling * Use modal to show detailed stats instead of alert * Changed styling and fixed fps = 0 (#2916) (React rendered 0 instead of for fps && {fps}) --------- Co-authored-by: Timo <16718859+toger5@users.noreply.github.com> --- locales/en/app.json | 1 + src/RTCConnectionStats.module.css | 20 ++++ src/RTCConnectionStats.tsx | 112 +++++++++++++++++++++ src/settings/DeveloperSettingsTab.tsx | 19 ++++ src/settings/settings.ts | 5 + src/state/MediaViewModel.ts | 139 ++++++++++++++++++++------ src/tile/GridTile.tsx | 8 ++ src/tile/MediaView.tsx | 11 ++ 8 files changed, 287 insertions(+), 28 deletions(-) create mode 100644 src/RTCConnectionStats.module.css create mode 100644 src/RTCConnectionStats.tsx diff --git a/locales/en/app.json b/locales/en/app.json index 0e71fd4e..f35c3579 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -73,6 +73,7 @@ "duplicate_tiles_label": "Number of additional tile copies per participant", "hostname": "Hostname: {{hostname}}", "matrix_id": "Matrix ID: {{id}}", + "show_connection_stats": "Show connection statistics", "show_non_member_tiles": "Show tiles for non-member media" }, "disconnected_banner": "Connectivity to the server has been lost.", diff --git a/src/RTCConnectionStats.module.css b/src/RTCConnectionStats.module.css new file mode 100644 index 00000000..0e29eaa9 --- /dev/null +++ b/src/RTCConnectionStats.module.css @@ -0,0 +1,20 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +.modal pre { + font-size: var(--font-size-micro); +} + +.statsPill { + border-radius: var(--media-view-border-radius); + grid-area: none; + position: absolute; + top: 0; + left: 0; + flex-direction: column; + align-items: flex-start; +} diff --git a/src/RTCConnectionStats.tsx b/src/RTCConnectionStats.tsx new file mode 100644 index 00000000..d092b677 --- /dev/null +++ b/src/RTCConnectionStats.tsx @@ -0,0 +1,112 @@ +/* +Copyright 2023, 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only +Please see LICENSE in the repository root for full details. +*/ + +import { useState, type FC } from "react"; +import { Button, Text } from "@vector-im/compound-web"; +import { + MicOnSolidIcon, + VideoCallSolidIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; +import classNames from "classnames"; + +import { Modal } from "./Modal"; +import styles from "./RTCConnectionStats.module.css"; +import mediaViewStyles from "../src/tile/MediaView.module.css"; +interface Props { + audio?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats; + video?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats; +} + +// This is only used in developer mode for debugging purposes, so we don't need full localization +export const RTCConnectionStats: FC = ({ audio, video, ...rest }) => { + const [showModal, setShowModal] = useState(false); + const [modalContents, setModalContents] = useState< + "video" | "audio" | "none" + >("none"); + + const showFullModal = (contents: "video" | "audio"): void => { + setShowModal(true); + setModalContents(contents); + }; + + const onDismissModal = (): void => { + setShowModal(false); + setModalContents("none"); + }; + return ( +
+ +
+
+            {modalContents !== "none" &&
+              JSON.stringify(
+                modalContents === "video" ? video : audio,
+                null,
+                2,
+              )}
+          
+
+
+ {audio && ( +
+ +
+ )} + {video && ( +
+ +
+ )} +
+ ); +}; diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index 057b0b0c..96ab262f 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -14,6 +14,7 @@ import { duplicateTiles as duplicateTilesSetting, debugTileLayout as debugTileLayoutSetting, showNonMemberTiles as showNonMemberTilesSetting, + showConnectionStats as showConnectionStatsSetting, } from "./settings"; import type { MatrixClient } from "matrix-js-sdk/src/client"; @@ -31,6 +32,10 @@ export const DeveloperSettingsTab: FC = ({ client }) => { showNonMemberTilesSetting, ); + const [showConnectionStats, setShowConnectionStats] = useSetting( + showConnectionStatsSetting, + ); + return ( <>

@@ -103,6 +108,20 @@ export const DeveloperSettingsTab: FC = ({ client }) => { )} /> + + ): void => { + setShowConnectionStats(event.target.checked); + }, + [setShowConnectionStats], + )} + /> + ); }; diff --git a/src/settings/settings.ts b/src/settings/settings.ts index ebb5dffc..24657ac4 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -78,6 +78,11 @@ export const showNonMemberTiles = new Setting( ); export const debugTileLayout = new Setting("debug-tile-layout", false); +export const showConnectionStats = new Setting( + "show-connection-stats", + false, +); + export const audioInput = new Setting( "audio-input", undefined, diff --git a/src/state/MediaViewModel.ts b/src/state/MediaViewModel.ts index b57b6f15..19a71786 100644 --- a/src/state/MediaViewModel.ts +++ b/src/state/MediaViewModel.ts @@ -47,7 +47,7 @@ import { useEffect } from "react"; import { ViewModel } from "./ViewModel"; import { useReactiveState } from "../useReactiveState"; -import { alwaysShowSelf } from "../settings/settings"; +import { alwaysShowSelf, showConnectionStats } from "../settings/settings"; import { accumulate } from "../utils/observable"; import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; import { E2eeType } from "../e2ee/e2eeType"; @@ -97,6 +97,60 @@ export function observeTrackReference$( ); } +export function observeRtpStreamStats$( + participant: Participant, + source: Track.Source, + type: "inbound-rtp" | "outbound-rtp", +): Observable< + RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats | undefined +> { + return combineLatest([ + observeTrackReference$(of(participant), source), + interval(1000).pipe(startWith(0)), + ]).pipe( + switchMap(async ([trackReference]) => { + const track = trackReference?.publication?.track; + if ( + !track || + !(track instanceof RemoteTrack || track instanceof LocalTrack) + ) { + return undefined; + } + const report = await track.getRTCStatsReport(); + if (!report) { + return undefined; + } + + for (const v of report.values()) { + if (v.type === type) { + return v; + } + } + + return undefined; + }), + startWith(undefined), + ); +} + +export function observeInboundRtpStreamStats$( + participant: Participant, + source: Track.Source, +): Observable { + return observeRtpStreamStats$(participant, source, "inbound-rtp").pipe( + map((x) => x as RTCInboundRtpStreamStats | undefined), + ); +} + +export function observeOutboundRtpStreamStats$( + participant: Participant, + source: Track.Source, +): Observable { + return observeRtpStreamStats$(participant, source, "outbound-rtp").pipe( + map((x) => x as RTCOutboundRtpStreamStats | undefined), + ); +} + function observeRemoteTrackReceivingOkay$( participant: Participant, source: Track.Source, @@ -111,33 +165,15 @@ function observeRemoteTrackReceivingOkay$( framesReceived: undefined, }; - return combineLatest([ - observeTrackReference$(of(participant), source), - interval(1000).pipe(startWith(0)), - ]).pipe( - switchMap(async ([trackReference]) => { - const track = trackReference?.publication?.track; - if (!track || !(track instanceof RemoteTrack)) { - return undefined; - } - const report = await track.getRTCStatsReport(); - if (!report) { - return undefined; - } - - for (const v of report.values()) { - if (v.type === "inbound-rtp") { - const { framesDecoded, framesDropped, framesReceived } = - v as RTCInboundRtpStreamStats; - return { - framesDecoded, - framesDropped, - framesReceived, - }; - } - } - - return undefined; + return observeInboundRtpStreamStats$(participant, source).pipe( + map((stats) => { + if (!stats) return undefined; + const { framesDecoded, framesDropped, framesReceived } = stats; + return { + framesDecoded, + framesDropped, + framesReceived, + }; }), filter((newStats) => !!newStats), map((newStats): boolean | undefined => { @@ -404,6 +440,13 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel { public get local(): boolean { return this instanceof LocalUserMediaViewModel; } + + public abstract get audioStreamStats$(): Observable< + RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats | undefined + >; + public abstract get videoStreamStats$(): Observable< + RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats | undefined + >; } /** @@ -453,6 +496,26 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel { reaction$, ); } + + public audioStreamStats$ = combineLatest([ + this.participant$, + showConnectionStats.value$, + ]).pipe( + switchMap(([p, showConnectionStats]) => { + if (!p || !showConnectionStats) return of(undefined); + return observeOutboundRtpStreamStats$(p, Track.Source.Microphone); + }), + ); + + public videoStreamStats$ = combineLatest([ + this.participant$, + showConnectionStats.value$, + ]).pipe( + switchMap(([p, showConnectionStats]) => { + if (!p || !showConnectionStats) return of(undefined); + return observeOutboundRtpStreamStats$(p, Track.Source.Camera); + }), + ); } /** @@ -542,6 +605,26 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel { public commitLocalVolume(): void { this.localVolumeCommit$.next(); } + + public audioStreamStats$ = combineLatest([ + this.participant$, + showConnectionStats.value$, + ]).pipe( + switchMap(([p, showConnectionStats]) => { + if (!p || !showConnectionStats) return of(undefined); + return observeInboundRtpStreamStats$(p, Track.Source.Microphone); + }), + ); + + public videoStreamStats$ = combineLatest([ + this.participant$, + showConnectionStats.value$, + ]).pipe( + switchMap(([p, showConnectionStats]) => { + if (!p || !showConnectionStats) return of(undefined); + return observeInboundRtpStreamStats$(p, Track.Source.Camera); + }), + ); } /** diff --git a/src/tile/GridTile.tsx b/src/tile/GridTile.tsx index 9eb775d0..0d33ccd6 100644 --- a/src/tile/GridTile.tsx +++ b/src/tile/GridTile.tsx @@ -86,6 +86,12 @@ const UserMediaTile = forwardRef( const video = useObservableEagerState(vm.video$); const unencryptedWarning = useObservableEagerState(vm.unencryptedWarning$); const encryptionStatus = useObservableEagerState(vm.encryptionStatus$); + const audioStreamStats = useObservableEagerState< + RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats | undefined + >(vm.audioStreamStats$); + const videoStreamStats = useObservableEagerState< + RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats | undefined + >(vm.videoStreamStats$); const audioEnabled = useObservableEagerState(vm.audioEnabled$); const videoEnabled = useObservableEagerState(vm.videoEnabled$); const speaking = useObservableEagerState(vm.speaking$); @@ -174,6 +180,8 @@ const UserMediaTile = forwardRef( currentReaction={reaction ?? undefined} raisedHandOnClick={raisedHandOnClick} localParticipant={vm.local} + audioStreamStats={audioStreamStats} + videoStreamStats={videoStreamStats} {...props} /> ); diff --git a/src/tile/MediaView.tsx b/src/tile/MediaView.tsx index 0d5341a8..abc3904b 100644 --- a/src/tile/MediaView.tsx +++ b/src/tile/MediaView.tsx @@ -22,6 +22,7 @@ import { RaisedHandIndicator } from "../reactions/RaisedHandIndicator"; import { showHandRaisedTimer, useSetting } from "../settings/settings"; import { type ReactionOption } from "../reactions"; import { ReactionIndicator } from "../reactions/ReactionIndicator"; +import { RTCConnectionStats } from "../RTCConnectionStats"; interface Props extends ComponentProps { className?: string; @@ -42,6 +43,8 @@ interface Props extends ComponentProps { currentReaction?: ReactionOption; raisedHandOnClick?: () => void; localParticipant: boolean; + audioStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats; + videoStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats; } export const MediaView = forwardRef( @@ -65,6 +68,8 @@ export const MediaView = forwardRef( currentReaction, raisedHandOnClick, localParticipant, + audioStreamStats, + videoStreamStats, ...props }, ref, @@ -125,6 +130,12 @@ export const MediaView = forwardRef( {t("video_tile.waiting_for_media")}

)} + {(audioStreamStats || videoStreamStats) && ( + + )} {/* TODO: Bring this back once encryption status is less broken */} {/*encryptionStatus !== EncryptionStatus.Okay && (
From d6942bc71d058f1a97962c1ba11958f28ad98291 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 11:34:37 +0000 Subject: [PATCH 71/72] Update all non-major dependencies (#2924) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 2370 +++++++++++++++++++++++++------------------------- 2 files changed, 1201 insertions(+), 1171 deletions(-) diff --git a/package.json b/package.json index 4649ee9c..ac693368 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@livekit/components-react": "^2.0.0", "@opentelemetry/api": "^1.4.0", "@opentelemetry/core": "^1.25.1", - "@opentelemetry/exporter-trace-otlp-http": "^0.56.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.57.0", "@opentelemetry/resources": "^1.25.1", "@opentelemetry/sdk-trace-base": "^1.25.1", "@opentelemetry/sdk-trace-web": "^1.9.1", diff --git a/yarn.lock b/yarn.lock index 120058a1..a74d404a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1014,10 +1014,10 @@ resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.10.0.tgz#1a67ac889c2d464a3492b3e54c38f80517963b16" integrity sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag== -"@codecov/bundler-plugin-core@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.6.0.tgz#59da9dc464752ac4ce6f1fa142261aa42f6a8092" - integrity sha512-x2M5P1NUk5lNW5slKY3jSb6Hpuie7bKaolDyZ7oWBHvBgtAJOeU7VrutdVhaiYoiQonM65JI2UAIWtw6mup/Yw== +"@codecov/bundler-plugin-core@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.7.0.tgz#5362cf332f38f2b9f1d30e81ce7fd47b531e3ebd" + integrity sha512-QsLwtwfy9KEe0CjqNE2Z/SPiCMn4CHAJ9cqTosZCX9YMKPi/WyFivv0pYE7CXA8ntG0l4Xc9kr36DUCuNRW0LQ== dependencies: "@actions/core" "^1.10.1" "@actions/github" "^6.0.0" @@ -1027,11 +1027,11 @@ zod "^3.22.4" "@codecov/vite-plugin@^1.3.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@codecov/vite-plugin/-/vite-plugin-1.6.0.tgz#5433600f1df8528d4ce693cc1ae9297b07b197d5" - integrity sha512-QwgFfF0FJMXovE/ZX33GqkBjkwUwzjPkWepwJizXiQD9emFS7iW82q1vPV9goiakJAvsCVm7Au9e7QnMBGJgvw== + version "1.7.0" + resolved "https://registry.yarnpkg.com/@codecov/vite-plugin/-/vite-plugin-1.7.0.tgz#5c584f5830a84af37cfd0974471f2999e7a92878" + integrity sha512-Cy/NxuEiGCf9X62dKewxnFf6Uv6CxfCCJAd8AfbOYbg96vwjwuq6BMoCIAk9gUFmwurw9EOKGPwoW0lIrUMIzA== dependencies: - "@codecov/bundler-plugin-core" "^1.6.0" + "@codecov/bundler-plugin-core" "^1.7.0" unplugin "^1.10.1" "@csstools/cascade-layer-name-parser@^2.0.4": @@ -1044,18 +1044,18 @@ resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.0.1.tgz#829f1c76f5800b79c51c709e2f36821b728e0e10" integrity sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA== -"@csstools/css-calc@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.0.tgz#3f28b8f8f736b8f78abbc75eebd55c756207e773" - integrity sha512-X69PmFOrjTZfN5ijxtI8hZ9kRADFSLrmmQ6hgDJ272Il049WGKpDY64KhrFm/7rbWve0z81QepawzjkKlqkNGw== +"@csstools/css-calc@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.1.tgz#a7dbc66627f5cf458d42aed14bda0d3860562383" + integrity sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag== -"@csstools/css-color-parser@^3.0.6": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.0.6.tgz#e646838f6aab4618aeea7ba0c4921a254e180276" - integrity sha512-S/IjXqTHdpI4EtzGoNCHfqraXF37x12ZZHA1Lk7zoT5pm2lMjFuqhX/89L7dqX4CcMacKK+6ZCs5TmEGb/+wKw== +"@csstools/css-color-parser@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz#442d61d58e54ad258d52c309a787fceb33906484" + integrity sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA== dependencies: "@csstools/color-helpers" "^5.0.1" - "@csstools/css-calc" "^2.1.0" + "@csstools/css-calc" "^2.1.1" "@csstools/css-parser-algorithms@^3.0.4": version "3.0.4" @@ -1080,23 +1080,23 @@ "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" -"@csstools/postcss-color-function@^4.0.6": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-4.0.6.tgz#dabd1e516ccd4c7bd5803e37075a503b5f7f0ac4" - integrity sha512-EcvXfC60cTIumzpsxWuvVjb7rsJEHPvqn3jeMEBUaE3JSc4FRuP7mEQ+1eicxWmIrs3FtzMH9gR3sgA5TH+ebQ== +"@csstools/postcss-color-function@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-4.0.7.tgz#d31d2044d8a4f8b3154ac54ac77014879eae9f56" + integrity sha512-aDHYmhNIHR6iLw4ElWhf+tRqqaXwKnMl0YsQ/X105Zc4dQwe6yJpMrTN6BwOoESrkDjOYMOfORviSSLeDTJkdQ== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/postcss-progressive-custom-properties" "^4.0.0" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-color-mix-function@^3.0.6": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.6.tgz#d971832ec30b3b60363bceddfeb4b90c7cc0f4b8" - integrity sha512-jVKdJn4+JkASYGhyPO+Wa5WXSx1+oUgaXb3JsjJn/BlrtFh5zjocCY7pwWi0nuP24V1fY7glQsxEYcYNy0dMFg== +"@csstools/postcss-color-mix-function@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.7.tgz#39735bbc84dc173061e4c2842ec656bb9bc6ed2e" + integrity sha512-e68Nev4CxZYCLcrfWhHH4u/N1YocOfTmw67/kVX5Rb7rnguqqLyxPjhHWjSBX8o4bmyuukmNf3wrUSU3//kT7g== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/postcss-progressive-custom-properties" "^4.0.0" @@ -1112,12 +1112,12 @@ "@csstools/postcss-progressive-custom-properties" "^4.0.0" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-exponential-functions@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.5.tgz#0c39f75df3357ee1e444b0aa0ede4e12aafea0e9" - integrity sha512-mi8R6dVfA2nDoKM3wcEi64I8vOYEgQVtVKCfmLHXupeLpACfGAided5ddMt5f+CnEodNu4DifuVwb0I6fQDGGQ== +"@csstools/postcss-exponential-functions@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.6.tgz#dcee86d22102576b13d8bea059125fbcf98e83cc" + integrity sha512-IgJA5DQsQLu/upA3HcdvC6xEMR051ufebBTIXZ5E9/9iiaA7juXWz1ceYj814lnDYP/7eWjZnw0grRJlX4eI6g== dependencies: - "@csstools/css-calc" "^2.1.0" + "@csstools/css-calc" "^2.1.1" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" @@ -1129,32 +1129,32 @@ "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-gamut-mapping@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.6.tgz#04ec6a50fdbca2a30dec56e6bb780c79621e47a7" - integrity sha512-0ke7fmXfc8H+kysZz246yjirAH6JFhyX9GTlyRnM0exHO80XcA9zeJpy5pOp5zo/AZiC/q5Pf+Hw7Pd6/uAoYA== +"@csstools/postcss-gamut-mapping@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.7.tgz#8aaa4b6ffb6e2187379a83d253607f988533be25" + integrity sha512-gzFEZPoOkY0HqGdyeBXR3JP218Owr683u7KOZazTK7tQZBE8s2yhg06W1tshOqk7R7SWvw9gkw2TQogKpIW8Xw== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" -"@csstools/postcss-gradients-interpolation-method@^5.0.6": - version "5.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.6.tgz#67fa61ada95e4534687fa76cd2d15ac74386560e" - integrity sha512-Itrbx6SLUzsZ6Mz3VuOlxhbfuyLTogG5DwEF1V8dAi24iMuvQPIHd7Ti+pNDp7j6WixndJGZaoNR0f9VSzwuTg== +"@csstools/postcss-gradients-interpolation-method@^5.0.7": + version "5.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.7.tgz#57e19d25e98aa028b98e22ef392ea24c3e61c568" + integrity sha512-WgEyBeg6glUeTdS2XT7qeTFBthTJuXlS9GFro/DVomj7W7WMTamAwpoP4oQCq/0Ki2gvfRYFi/uZtmRE14/DFA== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/postcss-progressive-custom-properties" "^4.0.0" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-hwb-function@^4.0.6": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.6.tgz#c40f557a54ed45e75c601a9ba7a08d315f64dbd7" - integrity sha512-927Pqy3a1uBP7U8sTfaNdZVB0mNXzIrJO/GZ8us9219q9n06gOqCdfZ0E6d1P66Fm0fYHvxfDbfcUuwAn5UwhQ== +"@csstools/postcss-hwb-function@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.7.tgz#d09528098c4b99c49c76de686a4ae35585acc691" + integrity sha512-LKYqjO+wGwDCfNIEllessCBWfR4MS/sS1WXO+j00KKyOjm7jDW2L6jzUmqASEiv/kkJO39GcoIOvTTfB3yeBUA== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/postcss-progressive-custom-properties" "^4.0.0" @@ -1222,12 +1222,12 @@ "@csstools/css-tokenizer" "^3.0.3" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-media-minmax@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.5.tgz#66970aa8d8057f84b88aff21f385194fbe03eb11" - integrity sha512-sdh5i5GToZOIAiwhdntRWv77QDtsxP2r2gXW/WbLSCoLr00KTq/yiF1qlQ5XX2+lmiFa8rATKMcbwl3oXDMNew== +"@csstools/postcss-media-minmax@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.6.tgz#427921c0f08033203810af16dfed0baedc538eab" + integrity sha512-J1+4Fr2W3pLZsfxkFazK+9kr96LhEYqoeBszLmFjb6AjYs+g9oDAw3J5oQignLKk3rC9XHW+ebPTZ9FaW5u5pg== dependencies: - "@csstools/css-calc" "^2.1.0" + "@csstools/css-calc" "^2.1.1" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/media-query-list-parser" "^4.0.2" @@ -1256,12 +1256,12 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-oklab-function@^4.0.6": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.6.tgz#17e8dfb6422dfd8d77256def5d5be8335ea7af34" - integrity sha512-Hptoa0uX+XsNacFBCIQKTUBrFKDiplHan42X73EklG6XmQLG7/aIvxoNhvZ7PvOWMt67Pw3bIlUY2nD6p5vL8A== +"@csstools/postcss-oklab-function@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.7.tgz#33b3322dfb27b0b5eb83a7ad36e67f08bc4e66cd" + integrity sha512-I6WFQIbEKG2IO3vhaMGZDkucbCaUSXMxvHNzDdnfsTCF5tc0UlV3Oe2AhamatQoKFjBi75dSEMrgWq3+RegsOQ== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/postcss-progressive-custom-properties" "^4.0.0" @@ -1274,21 +1274,21 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-random-function@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-random-function/-/postcss-random-function-1.0.1.tgz#73a0b62b5dbbc03c25a28f085235eb61b09a2fb0" - integrity sha512-Ab/tF8/RXktQlFwVhiC70UNfpFQRhtE5fQQoP2pO+KCPGLsLdWFiOuHgSRtBOqEshCVAzR4H6o38nhvRZq8deA== +"@csstools/postcss-random-function@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-random-function/-/postcss-random-function-1.0.2.tgz#699702820f19bb6b9632966ff44d8957db6889d2" + integrity sha512-vBCT6JvgdEkvRc91NFoNrLjgGtkLWt47GKT6E2UDn3nd8ZkMBiziQ1Md1OiKoSsgzxsSnGKG3RVdhlbdZEkHjA== dependencies: - "@csstools/css-calc" "^2.1.0" + "@csstools/css-calc" "^2.1.1" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" -"@csstools/postcss-relative-color-syntax@^3.0.6": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.6.tgz#4b8bc219b34b16f5abdbbcf09ac13e65bff6ef16" - integrity sha512-yxP618Xb+ji1I624jILaYM62uEmZcmbdmFoZHoaThw896sq0vU39kqTTF+ZNic9XyPtPMvq0vyvbgmHaszq8xg== +"@csstools/postcss-relative-color-syntax@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.7.tgz#862f8c6a2bbbab1a46aff8265b6a095fd267a3a6" + integrity sha512-apbT31vsJVd18MabfPOnE977xgct5B1I+Jpf+Munw3n6kKb1MMuUmGGH+PT9Hm/fFs6fe61Q/EWnkrb4bNoNQw== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/postcss-progressive-custom-properties" "^4.0.0" @@ -1301,21 +1301,21 @@ dependencies: postcss-selector-parser "^7.0.0" -"@csstools/postcss-sign-functions@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.0.tgz#a524fae1374b0e167729f612ca875d7b1b334262" - integrity sha512-SLcc20Nujx/kqbSwDmj6oaXgpy3UjFhBy1sfcqPgDkHfOIfUtUVH7OXO+j7BU4v/At5s61N5ZX6shvgPwluhsA== +"@csstools/postcss-sign-functions@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.1.tgz#eb8e4a5ac637982aeb9264cb99f85817612ad3e8" + integrity sha512-MslYkZCeMQDxetNkfmmQYgKCy4c+w9pPDfgOBCJOo/RI1RveEUdZQYtOfrC6cIZB7sD7/PHr2VGOcMXlZawrnA== dependencies: - "@csstools/css-calc" "^2.1.0" + "@csstools/css-calc" "^2.1.1" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" -"@csstools/postcss-stepped-value-functions@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.5.tgz#4d68633d502fbe2b6ef3898e368e3540488a0d8a" - integrity sha512-G6SJ6hZJkhxo6UZojVlLo14MohH4J5J7z8CRBrxxUYy9JuZiIqUo5TBYyDGcE0PLdzpg63a7mHSJz3VD+gMwqw== +"@csstools/postcss-stepped-value-functions@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.6.tgz#ee88c6122daf58a1b8641f462e8e33427c60b1f1" + integrity sha512-/dwlO9w8vfKgiADxpxUbZOWlL5zKoRIsCymYoh1IPuBsXODKanKnfuZRr32DEqT0//3Av1VjfNZU9yhxtEfIeA== dependencies: - "@csstools/css-calc" "^2.1.0" + "@csstools/css-calc" "^2.1.1" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" @@ -1327,12 +1327,12 @@ "@csstools/color-helpers" "^5.0.1" postcss-value-parser "^4.2.0" -"@csstools/postcss-trigonometric-functions@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.5.tgz#267b95a8bd45536e0360596b6da660a9eb6aac83" - integrity sha512-/YQThYkt5MLvAmVu7zxjhceCYlKrYddK6LEmK5I4ojlS6BmO9u2yO4+xjXzu2+NPYmHSTtP4NFSamBCMmJ1NJA== +"@csstools/postcss-trigonometric-functions@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.6.tgz#fc5c5f4c9bd0fd796b58b9a14d5d663be76d19fa" + integrity sha512-c4Y1D2Why/PeccaSouXnTt6WcNHJkoJRidV2VW9s5gJ97cNxnLgQ4Qj8qOqkIR9VmTQKJyNcbF4hy79ZQnWD7A== dependencies: - "@csstools/css-calc" "^2.1.0" + "@csstools/css-calc" "^2.1.1" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" @@ -1361,355 +1361,240 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== -"@esbuild/aix-ppc64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz#145b74d5e4a5223489cabdc238d8dad902df5259" - integrity sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ== - -"@esbuild/aix-ppc64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" - integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== +"@esbuild/aix-ppc64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461" + integrity sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA== "@esbuild/android-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== -"@esbuild/android-arm64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz#453bbe079fc8d364d4c5545069e8260228559832" - integrity sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ== - -"@esbuild/android-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" - integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== +"@esbuild/android-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz#f592957ae8b5643129fa889c79e69cd8669bb894" + integrity sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg== "@esbuild/android-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== -"@esbuild/android-arm@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.0.tgz#26c806853aa4a4f7e683e519cd9d68e201ebcf99" - integrity sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g== - -"@esbuild/android-arm@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" - integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== +"@esbuild/android-arm@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz#72d8a2063aa630308af486a7e5cbcd1e134335b3" + integrity sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q== "@esbuild/android-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== -"@esbuild/android-x64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.0.tgz#1e51af9a6ac1f7143769f7ee58df5b274ed202e6" - integrity sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ== - -"@esbuild/android-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" - integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== +"@esbuild/android-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz#9a7713504d5f04792f33be9c197a882b2d88febb" + integrity sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw== "@esbuild/darwin-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== -"@esbuild/darwin-arm64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz#d996187a606c9534173ebd78c58098a44dd7ef9e" - integrity sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow== - -"@esbuild/darwin-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" - integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== +"@esbuild/darwin-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz#02ae04ad8ebffd6e2ea096181b3366816b2b5936" + integrity sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA== "@esbuild/darwin-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== -"@esbuild/darwin-x64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz#30c8f28a7ef4e32fe46501434ebe6b0912e9e86c" - integrity sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ== - -"@esbuild/darwin-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" - integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== +"@esbuild/darwin-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz#9ec312bc29c60e1b6cecadc82bd504d8adaa19e9" + integrity sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA== "@esbuild/freebsd-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== -"@esbuild/freebsd-arm64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz#30f4fcec8167c08a6e8af9fc14b66152232e7fb4" - integrity sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw== - -"@esbuild/freebsd-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" - integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== +"@esbuild/freebsd-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz#5e82f44cb4906d6aebf24497d6a068cfc152fa00" + integrity sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg== "@esbuild/freebsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== -"@esbuild/freebsd-x64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz#1003a6668fe1f5d4439e6813e5b09a92981bc79d" - integrity sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ== - -"@esbuild/freebsd-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" - integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== +"@esbuild/freebsd-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz#3fb1ce92f276168b75074b4e51aa0d8141ecce7f" + integrity sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q== "@esbuild/linux-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== -"@esbuild/linux-arm64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz#3b9a56abfb1410bb6c9138790f062587df3e6e3a" - integrity sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw== - -"@esbuild/linux-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" - integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== +"@esbuild/linux-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz#856b632d79eb80aec0864381efd29de8fd0b1f43" + integrity sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg== "@esbuild/linux-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== -"@esbuild/linux-arm@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz#237a8548e3da2c48cd79ae339a588f03d1889aad" - integrity sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw== - -"@esbuild/linux-arm@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" - integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== +"@esbuild/linux-arm@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz#c846b4694dc5a75d1444f52257ccc5659021b736" + integrity sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA== "@esbuild/linux-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== -"@esbuild/linux-ia32@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz#4269cd19cb2de5de03a7ccfc8855dde3d284a238" - integrity sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA== - -"@esbuild/linux-ia32@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" - integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== +"@esbuild/linux-ia32@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz#f8a16615a78826ccbb6566fab9a9606cfd4a37d5" + integrity sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw== "@esbuild/linux-loong64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== -"@esbuild/linux-loong64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz#82b568f5658a52580827cc891cb69d2cb4f86280" - integrity sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A== - -"@esbuild/linux-loong64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" - integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== +"@esbuild/linux-loong64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz#1c451538c765bf14913512c76ed8a351e18b09fc" + integrity sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ== "@esbuild/linux-mips64el@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== -"@esbuild/linux-mips64el@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz#9a57386c926262ae9861c929a6023ed9d43f73e5" - integrity sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w== - -"@esbuild/linux-mips64el@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" - integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== +"@esbuild/linux-mips64el@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz#0846edeefbc3d8d50645c51869cc64401d9239cb" + integrity sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw== "@esbuild/linux-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== -"@esbuild/linux-ppc64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz#f3a79fd636ba0c82285d227eb20ed8e31b4444f6" - integrity sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw== - -"@esbuild/linux-ppc64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" - integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== +"@esbuild/linux-ppc64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz#8e3fc54505671d193337a36dfd4c1a23b8a41412" + integrity sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw== "@esbuild/linux-riscv64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== -"@esbuild/linux-riscv64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz#f9d2ef8356ce6ce140f76029680558126b74c780" - integrity sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw== - -"@esbuild/linux-riscv64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" - integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== +"@esbuild/linux-riscv64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz#6a1e92096d5e68f7bb10a0d64bb5b6d1daf9a694" + integrity sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q== "@esbuild/linux-s390x@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== -"@esbuild/linux-s390x@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz#45390f12e802201f38a0229e216a6aed4351dfe8" - integrity sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg== - -"@esbuild/linux-s390x@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" - integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== +"@esbuild/linux-s390x@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz#ab18e56e66f7a3c49cb97d337cd0a6fea28a8577" + integrity sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw== "@esbuild/linux-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== -"@esbuild/linux-x64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz#c8409761996e3f6db29abcf9b05bee8d7d80e910" - integrity sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ== +"@esbuild/linux-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz#8140c9b40da634d380b0b29c837a0b4267aff38f" + integrity sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q== -"@esbuild/linux-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" - integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== +"@esbuild/netbsd-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz#65f19161432bafb3981f5f20a7ff45abb2e708e6" + integrity sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw== "@esbuild/netbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== -"@esbuild/netbsd-x64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz#ba70db0114380d5f6cfb9003f1d378ce989cd65c" - integrity sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw== +"@esbuild/netbsd-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz#7a3a97d77abfd11765a72f1c6f9b18f5396bcc40" + integrity sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw== -"@esbuild/netbsd-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" - integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== - -"@esbuild/openbsd-arm64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz#72fc55f0b189f7a882e3cf23f332370d69dfd5db" - integrity sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ== - -"@esbuild/openbsd-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" - integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== +"@esbuild/openbsd-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz#58b00238dd8f123bfff68d3acc53a6ee369af89f" + integrity sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A== "@esbuild/openbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== -"@esbuild/openbsd-x64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz#b6ae7a0911c18fe30da3db1d6d17a497a550e5d8" - integrity sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg== - -"@esbuild/openbsd-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" - integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== +"@esbuild/openbsd-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz#0ac843fda0feb85a93e288842936c21a00a8a205" + integrity sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA== "@esbuild/sunos-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== -"@esbuild/sunos-x64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz#58f0d5e55b9b21a086bfafaa29f62a3eb3470ad8" - integrity sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA== - -"@esbuild/sunos-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" - integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== +"@esbuild/sunos-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz#8b7aa895e07828d36c422a4404cc2ecf27fb15c6" + integrity sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig== "@esbuild/win32-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== -"@esbuild/win32-arm64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz#b858b2432edfad62e945d5c7c9e5ddd0f528ca6d" - integrity sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ== - -"@esbuild/win32-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" - integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== +"@esbuild/win32-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz#c023afb647cabf0c3ed13f0eddfc4f1d61c66a85" + integrity sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ== "@esbuild/win32-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== -"@esbuild/win32-ia32@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz#167ef6ca22a476c6c0c014a58b4f43ae4b80dec7" - integrity sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA== - -"@esbuild/win32-ia32@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" - integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== +"@esbuild/win32-ia32@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz#96c356132d2dda990098c8b8b951209c3cd743c2" + integrity sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA== "@esbuild/win32-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== -"@esbuild/win32-x64@0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz#db44a6a08520b5f25bbe409f34a59f2d4bcc7ced" - integrity sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g== - -"@esbuild/win32-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" - integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== +"@esbuild/win32-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz#34aa0b52d0fbb1a654b596acfa595f0c7b77a77b" + integrity sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1805,55 +1690,55 @@ integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== "@fontsource/inconsolata@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@fontsource/inconsolata/-/inconsolata-5.1.0.tgz#f6a76680173336d02d2ce4009699821a6be239ce" - integrity sha512-vYPdG3R46MhK+99De8e8MMyNad5BAb1oTnHMpojlctZyWJIcin8bKHFPUpQSNRhZ4HQL/+DCW+RTiG2RbnweTw== + version "5.1.1" + resolved "https://registry.yarnpkg.com/@fontsource/inconsolata/-/inconsolata-5.1.1.tgz#bc5cc74d04dee8b2cb4e706cc33ac3dc50100191" + integrity sha512-jLLMagEJURTae5J30gehIsXRv96vjQ0XlALGxZC7DERWPqsJTa0oSsZR8k6IJfizU4ZeRl/aKWpZca2Lo3TvSg== "@fontsource/inter@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.0.tgz#ab629b2c662457022d2d6a29854b8dc8ba538c47" - integrity sha512-zKZR3kf1G0noIes1frLfOHP5EXVVm0M7sV/l9f/AaYf+M/DId35FO4LkigWjqWYjTJZGgplhdv4cB+ssvCqr5A== + version "5.1.1" + resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.1.tgz#401803b6ac4c877f5be94088aa89147ed5a2bd85" + integrity sha512-weN3E+rq0Xb3Z93VHJ+Rc7WOQX9ETJPTAJ+gDcaMHtjft67L58sfS65rAjC5tZUXQ2FdZ/V1/sSzCwZ6v05kJw== -"@formatjs/ecma402-abstract@2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz#cdeb3ffe1aeea9c4284b85b7e37e8e8615314c39" - integrity sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw== +"@formatjs/ecma402-abstract@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz#0ee291effe7ee2c340742a6c95d92eacb5e6c00a" + integrity sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg== dependencies: - "@formatjs/fast-memoize" "2.2.5" - "@formatjs/intl-localematcher" "0.5.9" + "@formatjs/fast-memoize" "2.2.6" + "@formatjs/intl-localematcher" "0.5.10" decimal.js "10" tslib "2" -"@formatjs/fast-memoize@2.2.5": - version "2.2.5" - resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz#54a4a1793d773b72c372d3dcab3595149aee7880" - integrity sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g== +"@formatjs/fast-memoize@2.2.6": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.6.tgz#fac0a84207a1396be1f1aa4ee2805b179e9343d1" + integrity sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw== dependencies: tslib "2" "@formatjs/intl-durationformat@^0.7.0": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@formatjs/intl-durationformat/-/intl-durationformat-0.7.1.tgz#d83e6f4bf188cafac50a2a911241084bafe89524" - integrity sha512-tM/sscHRcVMVAn0qMJlmq5mf3MaqA0jSz73NT4SYBHZuZqfU0EKWjJCwZBYeNRfvO6y20Yo0RzGxom0KvSVUlA== + version "0.7.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-durationformat/-/intl-durationformat-0.7.2.tgz#cb49e84b3be882341c21f1ffbb6a6320353bf506" + integrity sha512-GTO67hNFJOv7g3nOEpSJ+0CN0VHI/GvIfv0sTfTjl30aGN4oGV7SEo2IKCQe3NbPTAYUcCRCzKa5+vAW3crS5w== dependencies: - "@formatjs/ecma402-abstract" "2.3.1" - "@formatjs/intl-localematcher" "0.5.9" + "@formatjs/ecma402-abstract" "2.3.2" + "@formatjs/intl-localematcher" "0.5.10" tslib "2" -"@formatjs/intl-localematcher@0.5.9": - version "0.5.9" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz#43c6ee22be85b83340bcb09bdfed53657a2720db" - integrity sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA== +"@formatjs/intl-localematcher@0.5.10": + version "0.5.10" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz#1e0bd3fc1332c1fe4540cfa28f07e9227b659a58" + integrity sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q== dependencies: tslib "2" "@formatjs/intl-segmenter@^11.7.3": - version "11.7.7" - resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.7.tgz#8a5aaa316e11ca2d31b99222e6fcf1ab539b085e" - integrity sha512-610J5xz5DxtEpa16zNR89CrvA9qWHxQFkUB3FKiGao0Nwn7i8cl+oyBhuH9SvtXF9j2LUOM9VMdVCMzJkVANNw== + version "11.7.8" + resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.8.tgz#85990c7e3961ef686ed78b8cacb216852cadb061" + integrity sha512-+nqMCJ6LNLl+qXldE2uthF82O/2Yo6GZlyWbOY25fe3066iaHjmrR4nXd6AKRMCHNeBBx3rANFLm2B5cNTBzTQ== dependencies: - "@formatjs/ecma402-abstract" "2.3.1" - "@formatjs/intl-localematcher" "0.5.9" + "@formatjs/ecma402-abstract" "2.3.2" + "@formatjs/intl-localematcher" "0.5.10" tslib "2" "@gulpjs/to-absolute-glob@^4.0.0": @@ -2084,10 +1969,10 @@ dependencies: "@octokit/openapi-types" "^22.2.0" -"@opentelemetry/api-logs@0.56.0": - version "0.56.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.56.0.tgz#68f8c51ca905c260b610c8a3c67d3f9fa3d59a45" - integrity sha512-Wr39+94UNNG3Ei9nv3pHd4AJ63gq5nSemMRpCd8fPwDL9rN3vK26lzxfH27mw16XzOSO+TpyQwBAMaLxaPWG0g== +"@opentelemetry/api-logs@0.57.0": + version "0.57.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.57.0.tgz#794f9ff6343671f68e228eeb606d416c4ab29653" + integrity sha512-l1aJ30CXeauVYaI+btiynHpw341LthkMTv3omi1VJDX14werY2Wmv9n1yudMsq9HuY0m8PvXEVX4d8zxEb+WRg== dependencies: "@opentelemetry/api" "^1.3.0" @@ -2096,86 +1981,86 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/core@1.29.0", "@opentelemetry/core@^1.25.1": - version "1.29.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.29.0.tgz#a9397dfd9a8b37b2435b5e44be16d39ec1c82bd9" - integrity sha512-gmT7vAreXl0DTHD2rVZcw3+l2g84+5XiHIqdBUxXbExymPCvSsGOpiwMmn8nkiJur28STV31wnhIDrzWDPzjfA== +"@opentelemetry/core@1.30.0", "@opentelemetry/core@^1.25.1": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.30.0.tgz#ef959e11e137d72466e566e375ecc5a82e922b86" + integrity sha512-Q/3u/K73KUjTCnFUP97ZY+pBjQ1kPEgjOfXj/bJl8zW7GbXdkw6cwuyZk6ZTXkVgCBsYRYUzx4fvYK1jxdb9MA== dependencies: "@opentelemetry/semantic-conventions" "1.28.0" -"@opentelemetry/exporter-trace-otlp-http@^0.56.0": - version "0.56.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.56.0.tgz#184bd208d68bd19c3382a9a22737200b34f7edb9" - integrity sha512-vqVuJvcwameA0r0cNrRzrZqPLB0otS+95g0XkZdiKOXUo81wYdY6r4kyrwz4nSChqTBEFm0lqi/H2OWGboOa6g== +"@opentelemetry/exporter-trace-otlp-http@^0.57.0": + version "0.57.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.57.0.tgz#fee258d502dabda6646dd19029860192115017b6" + integrity sha512-BJl35PSkwoMlGEOrzjCG1ih6zqZoAZJIR4xyqSKC2BqPtwuRjID0vWBaEdP9xrxxJTEIEQw+gEY/0pUgicX0ew== dependencies: - "@opentelemetry/core" "1.29.0" - "@opentelemetry/otlp-exporter-base" "0.56.0" - "@opentelemetry/otlp-transformer" "0.56.0" - "@opentelemetry/resources" "1.29.0" - "@opentelemetry/sdk-trace-base" "1.29.0" + "@opentelemetry/core" "1.30.0" + "@opentelemetry/otlp-exporter-base" "0.57.0" + "@opentelemetry/otlp-transformer" "0.57.0" + "@opentelemetry/resources" "1.30.0" + "@opentelemetry/sdk-trace-base" "1.30.0" -"@opentelemetry/otlp-exporter-base@0.56.0": - version "0.56.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.56.0.tgz#3461fd403fbd3d366df46536a5a7dd7c7f499536" - integrity sha512-eURvv0fcmBE+KE1McUeRo+u0n18ZnUeSc7lDlW/dzlqFYasEbsztTK4v0Qf8C4vEY+aMTjPKUxBG0NX2Te3Pmw== +"@opentelemetry/otlp-exporter-base@0.57.0": + version "0.57.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.57.0.tgz#45a1c23edfc492723183f136cd7feb954f25ca28" + integrity sha512-QQl4Ngm3D6H8SDO0EM642ncTxjRsf/HDq7+IWIA0eaEK/NTsJeQ3iYJiZj3F4jkALnvyeM1kkwd+DHtqxTBx9Q== dependencies: - "@opentelemetry/core" "1.29.0" - "@opentelemetry/otlp-transformer" "0.56.0" + "@opentelemetry/core" "1.30.0" + "@opentelemetry/otlp-transformer" "0.57.0" -"@opentelemetry/otlp-transformer@0.56.0": - version "0.56.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.56.0.tgz#d2bae377ff2cabc0366d002ab993fcb8ea7d2700" - integrity sha512-kVkH/W2W7EpgWWpyU5VnnjIdSD7Y7FljQYObAQSKdRcejiwMj2glypZtUdfq1LTJcv4ht0jyTrw1D3CCxssNtQ== +"@opentelemetry/otlp-transformer@0.57.0": + version "0.57.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.57.0.tgz#7a4b6e3015397d975b9ea6de6068a072ef5880a7" + integrity sha512-yHX7sdwkdAmSa6Jbi3caSLDWy0PCHS1pKQeKz8AIWSyQqL7IojHKgdk9A+7eRd98Z1n9YTdwWSWLnObvIqhEhQ== dependencies: - "@opentelemetry/api-logs" "0.56.0" - "@opentelemetry/core" "1.29.0" - "@opentelemetry/resources" "1.29.0" - "@opentelemetry/sdk-logs" "0.56.0" - "@opentelemetry/sdk-metrics" "1.29.0" - "@opentelemetry/sdk-trace-base" "1.29.0" + "@opentelemetry/api-logs" "0.57.0" + "@opentelemetry/core" "1.30.0" + "@opentelemetry/resources" "1.30.0" + "@opentelemetry/sdk-logs" "0.57.0" + "@opentelemetry/sdk-metrics" "1.30.0" + "@opentelemetry/sdk-trace-base" "1.30.0" protobufjs "^7.3.0" -"@opentelemetry/resources@1.29.0", "@opentelemetry/resources@^1.25.1": - version "1.29.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.29.0.tgz#d170f39b2ac93d61b53d13dfcd96795181bdc372" - integrity sha512-s7mLXuHZE7RQr1wwweGcaRp3Q4UJJ0wazeGlc/N5/XSe6UyXfsh1UQGMADYeg7YwD+cEdMtU1yJAUXdnFzYzyQ== +"@opentelemetry/resources@1.30.0", "@opentelemetry/resources@^1.25.1": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.30.0.tgz#87604359e6195c017075b7d294a949ad018e692d" + integrity sha512-5mGMjL0Uld/99t7/pcd7CuVtJbkARckLVuiOX84nO8RtLtIz0/J6EOHM2TGvPZ6F4K+XjUq13gMx14w80SVCQg== dependencies: - "@opentelemetry/core" "1.29.0" + "@opentelemetry/core" "1.30.0" "@opentelemetry/semantic-conventions" "1.28.0" -"@opentelemetry/sdk-logs@0.56.0": - version "0.56.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.56.0.tgz#2ce3416111d1524305f4ec92dccf9e9f9e9626cf" - integrity sha512-OS0WPBJF++R/cSl+terUjQH5PebloidB1Jbbecgg2rnCmQbTST9xsRes23bLfDQVRvmegmHqDh884h0aRdJyLw== +"@opentelemetry/sdk-logs@0.57.0": + version "0.57.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.57.0.tgz#cf47ce698326e3d37084ef1aa97e0a0d45cba567" + integrity sha512-6Kbxdu/QE9LWH7+WSLmYo3DjAq+c55TiCLXiXu6b/2m2muy5SyOG2m0MrGqetyRpfYSSbIqHmJoqNVTN3+2a9g== dependencies: - "@opentelemetry/api-logs" "0.56.0" - "@opentelemetry/core" "1.29.0" - "@opentelemetry/resources" "1.29.0" + "@opentelemetry/api-logs" "0.57.0" + "@opentelemetry/core" "1.30.0" + "@opentelemetry/resources" "1.30.0" -"@opentelemetry/sdk-metrics@1.29.0": - version "1.29.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.29.0.tgz#26b9891e47715c0caaaa4d4e8b536685e1937a06" - integrity sha512-MkVtuzDjXZaUJSuJlHn6BSXjcQlMvHcsDV7LjY4P6AJeffMa4+kIGDjzsCf6DkAh6Vqlwag5EWEam3KZOX5Drw== +"@opentelemetry/sdk-metrics@1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.0.tgz#2a2aaa5d3a67cad5dbbfcf34cf7d990d11d109df" + integrity sha512-5kcj6APyRMvv6dEIP5plz2qfJAD4OMipBRT11u/pa1a68rHKI2Ln+iXVkAGKgx8o7CXbD7FdPypTUY88ZQgP4Q== dependencies: - "@opentelemetry/core" "1.29.0" - "@opentelemetry/resources" "1.29.0" + "@opentelemetry/core" "1.30.0" + "@opentelemetry/resources" "1.30.0" -"@opentelemetry/sdk-trace-base@1.29.0", "@opentelemetry/sdk-trace-base@^1.25.1": - version "1.29.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.29.0.tgz#f48d95dae0e58e601d0596bd2e482122d2688fb8" - integrity sha512-hEOpAYLKXF3wGJpXOtWsxEtqBgde0SCv+w+jvr3/UusR4ll3QrENEGnSl1WDCyRrpqOQ5NCNOvZch9UFVa7MnQ== +"@opentelemetry/sdk-trace-base@1.30.0", "@opentelemetry/sdk-trace-base@^1.25.1": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.0.tgz#27c68ab01b1cfb4af16356550f8091d6e727f182" + integrity sha512-RKQDaDIkV7PwizmHw+rE/FgfB2a6MBx+AEVVlAHXRG1YYxLiBpPX2KhmoB99R5vA4b72iJrjle68NDWnbrE9Dg== dependencies: - "@opentelemetry/core" "1.29.0" - "@opentelemetry/resources" "1.29.0" + "@opentelemetry/core" "1.30.0" + "@opentelemetry/resources" "1.30.0" "@opentelemetry/semantic-conventions" "1.28.0" "@opentelemetry/sdk-trace-web@^1.9.1": - version "1.29.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-web/-/sdk-trace-web-1.29.0.tgz#0d0321b511011a0174662bec821f046a55de51e8" - integrity sha512-PQVtJ76dsZ7HYBSlgZGIuxFtnKXxNbyHzMnRUxww7V2/6V/qtQN+cvNkqwPVffrUfbvClOnejo08NezAE1y+6g== + version "1.30.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-web/-/sdk-trace-web-1.30.0.tgz#d47478c45ae61dd0e5e88e284932b4be46e59c81" + integrity sha512-tSsPbaOQqmkfSkRkMnv1T8au2hwlv3v5ZUGmRwc7zIL1hokhZKg5qhqTsvrWvRENlZ7+J9+cXZFKIMNKHodyhQ== dependencies: - "@opentelemetry/core" "1.29.0" - "@opentelemetry/sdk-trace-base" "1.29.0" + "@opentelemetry/core" "1.30.0" + "@opentelemetry/sdk-trace-base" "1.30.0" "@opentelemetry/semantic-conventions" "1.28.0" "@opentelemetry/semantic-conventions@1.28.0", "@opentelemetry/semantic-conventions@^1.25.1": @@ -2380,14 +2265,14 @@ integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q== "@radix-ui/react-dialog@^1.0.4", "@radix-ui/react-dialog@^1.1.1": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.3.tgz#87cf49f619a6a0f6219980678be0f7c31978dee1" - integrity sha512-ujGvqQNkZ0J7caQyl8XuZRj2/TIrYcOGwqz5TeD1OMcCdfBuEMP0D12ve+8J5F9XuNUth3FAKFWo/wt0E/GJrQ== + version "1.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz#d68e977acfcc0d044b9dab47b6dd2c179d2b3191" + integrity sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA== dependencies: "@radix-ui/primitive" "1.1.1" "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-context" "1.1.1" - "@radix-ui/react-dismissable-layer" "1.1.2" + "@radix-ui/react-dismissable-layer" "1.1.3" "@radix-ui/react-focus-guards" "1.1.1" "@radix-ui/react-focus-scope" "1.1.1" "@radix-ui/react-id" "1.1.0" @@ -2397,24 +2282,13 @@ "@radix-ui/react-slot" "1.1.1" "@radix-ui/react-use-controllable-state" "1.1.0" aria-hidden "^1.1.1" - react-remove-scroll "2.6.0" + react-remove-scroll "^2.6.1" "@radix-ui/react-direction@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc" integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg== -"@radix-ui/react-dismissable-layer@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.2.tgz#771594b202f32bc8ffeb278c565f10c513814aee" - integrity sha512-kEHnlhv7wUggvhuJPkyw4qspXLJOdYoAP4dO2c8ngGuXTq1w/HZp1YeVB+NQ2KbH1iEG+pvOCGYSqh9HZOz6hg== - dependencies: - "@radix-ui/primitive" "1.1.1" - "@radix-ui/react-compose-refs" "1.1.1" - "@radix-ui/react-primitive" "2.0.1" - "@radix-ui/react-use-callback-ref" "1.1.0" - "@radix-ui/react-use-escape-keydown" "1.1.0" - "@radix-ui/react-dismissable-layer@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz#4ee0f0f82d53bf5bd9db21665799bb0d1bad5ed8" @@ -2710,236 +2584,236 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz#3e7eda4c0c1de6d2415343002d742ff95e38dca7" integrity sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA== -"@rollup/rollup-android-arm-eabi@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz#7f4c4d8cd5ccab6e95d6750dbe00321c1f30791e" - integrity sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ== +"@rollup/rollup-android-arm-eabi@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.2.tgz#d4c3965f18ebf567a99154f93bcf283fd527e2a9" + integrity sha512-s/8RiF4bdmGnc/J0N7lHAr5ZFJj+NdJqJ/Hj29K+c4lEdoVlukzvWXB9XpWZCdakVT0YAw8iyIqUP2iFRz5/jA== "@rollup/rollup-android-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz#04f679231acf7284f1f8a1f7250d0e0944865ba8" integrity sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg== -"@rollup/rollup-android-arm64@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz#17ea71695fb1518c2c324badbe431a0bd1879f2d" - integrity sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA== +"@rollup/rollup-android-arm64@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.2.tgz#cbc7e636a7aab984161fc045039bf3c6abb50083" + integrity sha512-mKRlVj1KsKWyEOwR6nwpmzakq6SgZXW4NUHNWlYSiyncJpuXk7wdLzuKdWsRoR1WLbWsZBKvsUCdCTIAqRn9cA== "@rollup/rollup-darwin-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz#ecea723041621747d0772af93b54752edf26467a" integrity sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg== -"@rollup/rollup-darwin-arm64@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz#dac0f0d0cfa73e7d5225ae6d303c13c8979e7999" - integrity sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ== +"@rollup/rollup-darwin-arm64@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.2.tgz#d084c6120f96749a7ddc5ef81d8740f2525abb6e" + integrity sha512-vJX+vennGwygmutk7N333lvQ/yKVAHnGoBS2xMRQgXWW8tvn46YWuTDOpKroSPR9BEW0Gqdga2DHqz8Pwk6X5w== "@rollup/rollup-darwin-x64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz#28e6e0687092f31e20982fc104779d48c643fc21" integrity sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA== -"@rollup/rollup-darwin-x64@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz#8f63baa1d31784904a380d2e293fa1ddf53dd4a2" - integrity sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ== +"@rollup/rollup-darwin-x64@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.2.tgz#1393f12d5722cc39b8c014aedd4b4da8043929a9" + integrity sha512-e2rW9ng5O6+Mt3ht8fH0ljfjgSCC6ffmOipiLUgAnlK86CHIaiCdHCzHzmTkMj6vEkqAiRJ7ss6Ibn56B+RE5w== "@rollup/rollup-freebsd-arm64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz#99e9173b8aef3d1ef086983da70413988206e530" integrity sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g== -"@rollup/rollup-freebsd-arm64@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz#30ed247e0df6e8858cdc6ae4090e12dbeb8ce946" - integrity sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA== +"@rollup/rollup-freebsd-arm64@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.2.tgz#1c11650970c4b52d7fb077f5a4a6e16ba5e6db4f" + integrity sha512-/xdNwZe+KesG6XJCK043EjEDZTacCtL4yurMZRLESIgHQdvtNyul3iz2Ab03ZJG0pQKbFTu681i+4ETMF9uE/Q== "@rollup/rollup-freebsd-x64@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz#f3a1ef941f8d3c6b2b036484c69a7b2d3d9ebbd7" integrity sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw== -"@rollup/rollup-freebsd-x64@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz#57846f382fddbb508412ae07855b8a04c8f56282" - integrity sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ== +"@rollup/rollup-freebsd-x64@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.2.tgz#d3d79a2b96e81475571cb9bb414910450bcebe04" + integrity sha512-eXKvpThGzREuAbc6qxnArHh8l8W4AyTcL8IfEnmx+bcnmaSGgjyAHbzZvHZI2csJ+e0MYddl7DX0X7g3sAuXDQ== "@rollup/rollup-linux-arm-gnueabihf@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz#9ba6adcc33f26f2a0c6ee658f0bbda4de8da2f75" integrity sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA== -"@rollup/rollup-linux-arm-gnueabihf@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz#378ca666c9dae5e6f94d1d351e7497c176e9b6df" - integrity sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA== +"@rollup/rollup-linux-arm-gnueabihf@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.2.tgz#14a39257111abbc38412805c9162819d3bb248c1" + integrity sha512-h4VgxxmzmtXLLYNDaUcQevCmPYX6zSj4SwKuzY7SR5YlnCBYsmvfYORXgiU8axhkFCDtQF3RW5LIXT8B14Qykg== "@rollup/rollup-linux-arm-musleabihf@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz#62f2426fa9016ec884f4fa779d7b62d5ba02a41a" integrity sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ== -"@rollup/rollup-linux-arm-musleabihf@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz#a692eff3bab330d5c33a5d5813a090c15374cddb" - integrity sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg== +"@rollup/rollup-linux-arm-musleabihf@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.2.tgz#64304812643515c0ed83cdaf4fde034c35dbc776" + integrity sha512-EObwZ45eMmWZQ1w4N7qy4+G1lKHm6mcOwDa+P2+61qxWu1PtQJ/lz2CNJ7W3CkfgN0FQ7cBUy2tk6D5yR4KeXw== "@rollup/rollup-linux-arm64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz#f98ec111a231d35e0c6d3404e3d80f67f9d5b9f8" integrity sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A== -"@rollup/rollup-linux-arm64-gnu@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz#6b1719b76088da5ac1ae1feccf48c5926b9e3db9" - integrity sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA== +"@rollup/rollup-linux-arm64-gnu@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.2.tgz#60d457954c288c168049aadb304c204d8a680236" + integrity sha512-Z7zXVHEXg1elbbYiP/29pPwlJtLeXzjrj4241/kCcECds8Zg9fDfURWbZHRIKrEriAPS8wnVtdl4ZJBvZr325w== "@rollup/rollup-linux-arm64-musl@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz#4b36ffb8359f959f2c29afd187603c53368b6723" integrity sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw== -"@rollup/rollup-linux-arm64-musl@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz#865baf5b6f5ff67acb32e5a359508828e8dc5788" - integrity sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A== +"@rollup/rollup-linux-arm64-musl@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.2.tgz#17deb5058243bc5599164a9e0a899b0902310fca" + integrity sha512-TF4kxkPq+SudS/r4zGPf0G08Bl7+NZcFrUSR3484WwsHgGgJyPQRLCNrQ/R5J6VzxfEeQR9XRpc8m2t7lD6SEQ== -"@rollup/rollup-linux-loongarch64-gnu@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz#23c6609ba0f7fa7a7f2038b6b6a08555a5055a87" - integrity sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA== +"@rollup/rollup-linux-loongarch64-gnu@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.2.tgz#5c65dd6557fda1f45c285cfeb4c5eda4c868341d" + integrity sha512-kO9Fv5zZuyj2zB2af4KA29QF6t7YSxKrY7sxZXfw8koDQj9bx5Tk5RjH+kWKFKok0wLGTi4bG117h31N+TIBEg== "@rollup/rollup-linux-powerpc64le-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz#52f4b39e6783505d168a745b79d86474fde71680" integrity sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA== -"@rollup/rollup-linux-powerpc64le-gnu@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz#652ef0d9334a9f25b9daf85731242801cb0fc41c" - integrity sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A== +"@rollup/rollup-linux-powerpc64le-gnu@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.2.tgz#33e45cac222fa6d09891f73bfb2d5d027ec34989" + integrity sha512-gIh776X7UCBaetVJGdjXPFurGsdWwHHinwRnC5JlLADU8Yk0EdS/Y+dMO264OjJFo7MXQ5PX4xVFbxrwK8zLqA== "@rollup/rollup-linux-riscv64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz#49195be7e6a7d68d482b12461e2ea914e31ff977" integrity sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA== -"@rollup/rollup-linux-riscv64-gnu@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz#1eb6651839ee6ebca64d6cc64febbd299e95e6bd" - integrity sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA== +"@rollup/rollup-linux-riscv64-gnu@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.2.tgz#1edee7a06538597720c4bf8178367d4b5651717d" + integrity sha512-YgikssQ5UNq1GoFKZydMEkhKbjlUq7G3h8j6yWXLBF24KyoA5BcMtaOUAXq5sydPmOPEqB6kCyJpyifSpCfQ0w== "@rollup/rollup-linux-s390x-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz#4b8d50a205eac7b46cdcb9c50d4a6ae5994c02e0" integrity sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ== -"@rollup/rollup-linux-s390x-gnu@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz#015c52293afb3ff2a293cf0936b1d43975c1e9cd" - integrity sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg== +"@rollup/rollup-linux-s390x-gnu@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.2.tgz#0ad4aaae2fd89c3607b743c63514c4561905672b" + integrity sha512-9ouIR2vFWCyL0Z50dfnon5nOrpDdkTG9lNDs7MRaienQKlTyHcDxplmk3IbhFlutpifBSBr2H4rVILwmMLcaMA== "@rollup/rollup-linux-x64-gnu@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz#dfcceebc5ccac7fc2db19471996026258c81b55f" integrity sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig== -"@rollup/rollup-linux-x64-gnu@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz#b83001b5abed2bcb5e2dbeec6a7e69b194235c1e" - integrity sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw== +"@rollup/rollup-linux-x64-gnu@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.2.tgz#ae27f8d26c02d8ce6f84275860e99678b9f3e932" + integrity sha512-ckBBNRN/F+NoSUDENDIJ2U9UWmIODgwDB/vEXCPOMcsco1niTkxTXa6D2Y/pvCnpzaidvY2qVxGzLilNs9BSzw== "@rollup/rollup-linux-x64-musl@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz#192f78bad8429711d63a31dc0a7d3312e2df850e" integrity sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ== -"@rollup/rollup-linux-x64-musl@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz#6cc7c84cd4563737f8593e66f33b57d8e228805b" - integrity sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g== +"@rollup/rollup-linux-x64-musl@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.2.tgz#24ff1a64dddd75c489bd8714bcb5a659769d3e4a" + integrity sha512-jycl1wL4AgM2aBFJFlpll/kGvAjhK8GSbEmFT5v3KC3rP/b5xZ1KQmv0vQQ8Bzb2ieFQ0kZFPRMbre/l3Bu9JA== "@rollup/rollup-win32-arm64-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz#f4ec076579634f780b4e5896ae7f59f3e38e0c60" integrity sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww== -"@rollup/rollup-win32-arm64-msvc@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz#631ffeee094d71279fcd1fe8072bdcf25311bc11" - integrity sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A== +"@rollup/rollup-win32-arm64-msvc@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.2.tgz#7c56efd576618db251909e21818d473cbcf96786" + integrity sha512-S2V0LlcOiYkNGlRAWZwwUdNgdZBfvsDHW0wYosYFV3c7aKgEVcbonetZXsHv7jRTTX+oY5nDYT4W6B1oUpMNOg== "@rollup/rollup-win32-ia32-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz#5458eab1929827e4f805cefb90bd09ecf7eeed2b" integrity sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg== -"@rollup/rollup-win32-ia32-msvc@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz#06d1d60d5b9f718e8a6c4a43f82e3f9e3254587f" - integrity sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA== +"@rollup/rollup-win32-ia32-msvc@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.2.tgz#0b77e11129b04bb5b9bfff4b011d084a975190e0" + integrity sha512-pW8kioj9H5f/UujdoX2atFlXNQ9aCfAxFRaa+mhczwcsusm6gGrSo4z0SLvqLF5LwFqFTjiLCCzGkNK/LE0utQ== "@rollup/rollup-win32-x64-msvc@4.25.0": version "4.25.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz#93415e7e707e4b156d77c5950b983b58f4bc33f3" integrity sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg== -"@rollup/rollup-win32-x64-msvc@4.28.1": - version "4.28.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz#4dff5c4259ebe6c5b4a8f2c5bc3829b7a8447ff0" - integrity sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA== +"@rollup/rollup-win32-x64-msvc@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.2.tgz#dc89d92418ae2efa1d70e071c686cffbcf788147" + integrity sha512-p6fTArexECPf6KnOHvJXRpAEq0ON1CBtzG/EY4zw08kCHk/kivBc5vUEtnCFNCHOpJZ2ne77fxwRLIKD4wuW2Q== "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sentry-internal/browser-utils@8.45.0": - version "8.45.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.45.0.tgz#8e9217b8e8a4242c9a8244dce648289eaa1e38a0" - integrity sha512-MX/E/C+W5I9jkGD1PsbZ2hpCc7YuizNKmEbuGPxQPfUSIPrdE2wpo6ZfIhEbxq9m/trl1oRCN4PXi3BB7dlYYg== +"@sentry-internal/browser-utils@8.47.0": + version "8.47.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.47.0.tgz#39f2766a1bbdffc2d211e2f61f8ed8c258245b3d" + integrity sha512-vOXzYzHTKkahTLDzWWIA4EiVCQ+Gk+7xGWUlNcR2ZiEPBqYZVb5MjsUozAcc7syrSUy6WicyFjcomZ3rlCVQhg== dependencies: - "@sentry/core" "8.45.0" + "@sentry/core" "8.47.0" -"@sentry-internal/feedback@8.45.0": - version "8.45.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.45.0.tgz#cfd7f54d5089682a2768c1229a5efcda4d9561fe" - integrity sha512-WerpfkKrKPAlnQuqjEgKXZtrx68cla7GyOkNOeL40JQbY4/By4Qjx1atUOmgk/FdjrCLPw+jQQY9pXRpMRqqRw== +"@sentry-internal/feedback@8.47.0": + version "8.47.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.47.0.tgz#22bceac03b61ab8509e79c0875fb140f214b7c4f" + integrity sha512-IAiIemTQIalxAOYhUENs9bZ8pMNgJnX3uQSuY7v0gknEqClOGpGkG04X/cxCmtJUj1acZ9ShTGDxoh55a+ggAQ== dependencies: - "@sentry/core" "8.45.0" + "@sentry/core" "8.47.0" -"@sentry-internal/replay-canvas@8.45.0": - version "8.45.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.45.0.tgz#46f39402ff0cfee4ae05191af20b4e4fac6f474c" - integrity sha512-LZ8kBuzO5gutDiWnCyYEzBMDLq9PIllcsWsXRpKoau0Zqs3DbyRolI11dNnxmUSh7UW21FksxBpqn5yPmUMbag== +"@sentry-internal/replay-canvas@8.47.0": + version "8.47.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.47.0.tgz#5bbd04c81235b2bf627aa216185ae1993d2102c4" + integrity sha512-M4W9UGouEeELbGbP3QsXLDVtGiQSZoWJlKwqMWyqdQgZuLoKw0S33+60t6teLVMhuQZR0UI9VJTF5coiXysnnA== dependencies: - "@sentry-internal/replay" "8.45.0" - "@sentry/core" "8.45.0" + "@sentry-internal/replay" "8.47.0" + "@sentry/core" "8.47.0" -"@sentry-internal/replay@8.45.0": - version "8.45.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.45.0.tgz#e94d250de235491888694f7cf0f637114adb4b9a" - integrity sha512-SOFwFpzx0B6lxhLl2hBnxvybo7gdB5TMY8dOHMwXgk5A2+BXvSpvWXnr33yqUlBmC8R3LeFTB3C0plzM5lhkJg== +"@sentry-internal/replay@8.47.0": + version "8.47.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.47.0.tgz#4f7bd359df2de25d919a378295cab67dfa05a406" + integrity sha512-G/S40ZBORj0HSMLw/uVC6YDEPN/dqVk901vf4VYfml686DEhJrZesfAfp5SydJumQ0NKZQrdtvny+BWnlI5H1w== dependencies: - "@sentry-internal/browser-utils" "8.45.0" - "@sentry/core" "8.45.0" + "@sentry-internal/browser-utils" "8.47.0" + "@sentry/core" "8.47.0" "@sentry/babel-plugin-component-annotate@2.22.7": version "2.22.7" resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.7.tgz#604c7e33d48528a13477e7af597c4d5fca51b8bd" integrity sha512-aa7XKgZMVl6l04NY+3X7BP7yvQ/s8scn8KzQfTLrGRarziTlMGrsCOBQtCNWXOPEbtxAIHpZ9dsrAn5EJSivOQ== -"@sentry/browser@8.45.0": - version "8.45.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.45.0.tgz#2e8f7b8b1a7860863aae4d716b9748a21789f0e0" - integrity sha512-Y+BcfpXY1eEkOYOzgLGkx1YH940uMAymYOxfSZSvC+Vx6xHuaGT05mIFef/aeZbyu2AUs6JjdvD1BRBZlHg78w== +"@sentry/browser@8.47.0": + version "8.47.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.47.0.tgz#fe0b6b65c0394f54438d6704039adeaec214ce18" + integrity sha512-K6BzHisykmbFy/wORtGyfsAlw7ShevLALzu3ReZZZ18dVubO1bjSNjkZQU9MJD5Jcb9oLwkq89n3N9XIBfvdRA== dependencies: - "@sentry-internal/browser-utils" "8.45.0" - "@sentry-internal/feedback" "8.45.0" - "@sentry-internal/replay" "8.45.0" - "@sentry-internal/replay-canvas" "8.45.0" - "@sentry/core" "8.45.0" + "@sentry-internal/browser-utils" "8.47.0" + "@sentry-internal/feedback" "8.47.0" + "@sentry-internal/replay" "8.47.0" + "@sentry-internal/replay-canvas" "8.47.0" + "@sentry/core" "8.47.0" "@sentry/bundler-plugin-core@2.22.7": version "2.22.7" @@ -3009,18 +2883,18 @@ "@sentry/cli-win32-i686" "2.39.1" "@sentry/cli-win32-x64" "2.39.1" -"@sentry/core@8.45.0": - version "8.45.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.45.0.tgz#a03a1b666989898ce7fb33f9ec279ea08450b317" - integrity sha512-4YTuBipWSh4JrtSYS5GxUQBAcAgOIkEoFfFbwVcr3ivijOacJLRXTBn3rpcy1CKjBq0PHDGR+2RGRYC+bNAMxg== +"@sentry/core@8.47.0": + version "8.47.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.47.0.tgz#e811444552f7a91b5de573875a318a6cd4e802f8" + integrity sha512-iSEJZMe3DOcqBFZQAqgA3NB2lCWBc4Gv5x/SCri/TVg96wAlss4VrUunSI2Mp0J4jJ5nJcJ2ChqHSBAU48k3FA== "@sentry/react@^8.0.0": - version "8.45.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.45.0.tgz#9a1bfbbbb3575fffb92796acc28ad5bb93a6855a" - integrity sha512-xuJBDATJKAHOxpR5IBfGFWJxXb05GMPGGpk8UoWai1Mh50laAQ0/WW+5sDAKrCjXoA+JZ6fb3DP8EE2X93n1nw== + version "8.47.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.47.0.tgz#0d9c120b0d4a2efd6d8e8fb9acff332c63afd50d" + integrity sha512-SRk2Up+qBTow4rQGiRXViC2i4M5w/tae5w8I/rmX+IxFoPyh8wXERcLAj/8xbbRm8aR+A4i5gNgfFtrYsyFJFA== dependencies: - "@sentry/browser" "8.45.0" - "@sentry/core" "8.45.0" + "@sentry/browser" "8.47.0" + "@sentry/core" "8.47.0" hoist-non-react-statics "^3.3.2" "@sentry/vite-plugin@^2.0.0": @@ -3284,9 +3158,9 @@ undici-types "~6.19.8" "@types/node@^22.0.0": - version "22.10.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.2.tgz#a485426e6d1fdafc7b0d4c7b24e2c78182ddabb9" - integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== + version "22.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.5.tgz#95af89a3fb74a2bb41ef9927f206e6472026e48b" + integrity sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ== dependencies: undici-types "~6.20.0" @@ -3385,15 +3259,15 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^8.0.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz#0901933326aea4443b81df3f740ca7dfc45c7bea" - integrity sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw== + version "8.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz#2b1e1b791e21d5fc27ddc93884db066444f597b5" + integrity sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/type-utils" "8.18.0" - "@typescript-eslint/utils" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" + "@typescript-eslint/scope-manager" "8.19.0" + "@typescript-eslint/type-utils" "8.19.0" + "@typescript-eslint/utils" "8.19.0" + "@typescript-eslint/visitor-keys" "8.19.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" @@ -3407,14 +3281,14 @@ "@typescript-eslint/utils" "5.62.0" "@typescript-eslint/parser@^8.0.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.18.0.tgz#a1c9456cbb6a089730bf1d3fc47946c5fb5fe67b" - integrity sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q== + version "8.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.19.0.tgz#f1512e6e5c491b03aabb2718b95becde22b15292" + integrity sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw== dependencies: - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/typescript-estree" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" + "@typescript-eslint/scope-manager" "8.19.0" + "@typescript-eslint/types" "8.19.0" + "@typescript-eslint/typescript-estree" "8.19.0" + "@typescript-eslint/visitor-keys" "8.19.0" debug "^4.3.4" "@typescript-eslint/scope-manager@5.62.0": @@ -3425,21 +3299,21 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz#30b040cb4557804a7e2bcc65cf8fdb630c96546f" - integrity sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw== +"@typescript-eslint/scope-manager@8.19.0": + version "8.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz#28fa413a334f70e8b506a968531e0a7c9c3076dc" + integrity sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA== dependencies: - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" + "@typescript-eslint/types" "8.19.0" + "@typescript-eslint/visitor-keys" "8.19.0" -"@typescript-eslint/type-utils@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz#6f0d12cf923b6fd95ae4d877708c0adaad93c471" - integrity sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow== +"@typescript-eslint/type-utils@8.19.0": + version "8.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz#41abd7d2e4cf93b6854b1fe6cbf416fab5abf89f" + integrity sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg== dependencies: - "@typescript-eslint/typescript-estree" "8.18.0" - "@typescript-eslint/utils" "8.18.0" + "@typescript-eslint/typescript-estree" "8.19.0" + "@typescript-eslint/utils" "8.19.0" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -3448,10 +3322,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.18.0.tgz#3afcd30def8756bc78541268ea819a043221d5f3" - integrity sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA== +"@typescript-eslint/types@8.19.0": + version "8.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.19.0.tgz#a190a25c5484a42b81eaad06989579fdeb478cbb" + integrity sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -3466,13 +3340,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz#d8ca785799fbb9c700cdff1a79c046c3e633c7f9" - integrity sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg== +"@typescript-eslint/typescript-estree@8.19.0": + version "8.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz#6b4f48f98ffad6597379951b115710f4d68c9ccb" + integrity sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw== dependencies: - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" + "@typescript-eslint/types" "8.19.0" + "@typescript-eslint/visitor-keys" "8.19.0" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -3494,15 +3368,15 @@ eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/utils@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.18.0.tgz#48f67205d42b65d895797bb7349d1be5c39a62f7" - integrity sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg== +"@typescript-eslint/utils@8.19.0": + version "8.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.19.0.tgz#33824310e1fccc17f27fbd1030fd8bbd9a674684" + integrity sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/typescript-estree" "8.18.0" + "@typescript-eslint/scope-manager" "8.19.0" + "@typescript-eslint/types" "8.19.0" + "@typescript-eslint/typescript-estree" "8.19.0" "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" @@ -3512,12 +3386,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz#7b6d33534fa808e33a19951907231ad2ea5c36dd" - integrity sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw== +"@typescript-eslint/visitor-keys@8.19.0": + version "8.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz#dc313f735e64c4979c9073f51ffcefb6d9be5c77" + integrity sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w== dependencies: - "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/types" "8.19.0" eslint-visitor-keys "^4.2.0" "@ungap/structured-clone@^1.2.0": @@ -3764,13 +3638,13 @@ aria-query@^5.0.0, aria-query@^5.3.2: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" + call-bound "^1.0.3" + is-array-buffer "^3.0.5" array-includes@^3.1.6, array-includes@^3.1.8: version "3.1.8" @@ -3813,7 +3687,17 @@ array.prototype.findlastindex@^1.2.5: es-object-atoms "^1.0.0" es-shim-unscopables "^1.0.2" -array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: +array.prototype.flat@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" + integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== @@ -3833,6 +3717,16 @@ array.prototype.flatmap@^1.3.2: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" +array.prototype.flatmap@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" + integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" + array.prototype.tosorted@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" @@ -3844,19 +3738,18 @@ array.prototype.tosorted@^1.1.4: es-errors "^1.3.0" es-shim-unscopables "^1.0.2" -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== dependencies: array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" + call-bind "^1.0.8" define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" assertion-error@^2.0.1: version "2.0.1" @@ -3908,9 +3801,9 @@ axobject-query@^4.1.0: integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== b4a@^1.6.4: - version "1.6.6" - resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba" - integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg== + version "1.6.7" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" + integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg== babel-plugin-polyfill-corejs2@^0.4.10: version "0.4.11" @@ -3950,9 +3843,9 @@ balanced-match@^1.0.0: integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== bare-events@^2.2.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.4.2.tgz#3140cca7a0e11d49b3edc5041ab560659fd8e1f8" - integrity sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q== + version "2.5.1" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.1.tgz#052a96e3fc0e87cd9f226199d7f8a80cd87b6d21" + integrity sha512-Bw2PgKSrZ3uCuSV9WQ998c/GTJTd+9bWj97n7aDQMP8dP/exAZQlJeswPty0ISy+HZD+9Ex+C7CCnc9Q5QJFmQ== base-x@^5.0.0: version "5.0.0" @@ -4101,16 +3994,31 @@ cac@^6.7.14: resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" + integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== dependencies: - es-define-property "^1.0.0" es-errors "^1.3.0" function-bind "^1.1.2" + +call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" get-intrinsic "^1.2.4" - set-function-length "^1.2.1" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681" + integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA== + dependencies: + call-bind-apply-helpers "^1.0.1" + get-intrinsic "^1.2.6" callsites@^3.0.0: version "3.1.0" @@ -4127,7 +4035,12 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669, caniuse-lite@^1.0.30001688: +caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688: + version "1.0.30001690" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8" + integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w== + +caniuse-lite@^1.0.30001669: version "1.0.30001688" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz#f9d3ede749f083ce0db4c13db9d828adaf2e8d0a" integrity sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA== @@ -4228,9 +4141,9 @@ chokidar@^3.5.3: fsevents "~2.3.2" chokidar@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41" - integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA== + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== dependencies: readdirp "^4.0.1" @@ -4318,7 +4231,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colors@1.4.0: +colors@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -4330,16 +4243,16 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@~12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== - common-tags@^1.8.0: version "1.8.2" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" @@ -4491,30 +4404,30 @@ data-urls@^5.0.0: whatwg-mimetype "^4.0.0" whatwg-url "^14.0.0" -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== dependencies: - call-bind "^1.0.6" + call-bound "^1.0.3" es-errors "^1.3.0" - is-data-view "^1.0.1" + is-data-view "^1.0.2" -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== dependencies: - call-bind "^1.0.7" + call-bound "^1.0.3" es-errors "^1.3.0" - is-data-view "^1.0.1" + is-data-view "^1.0.2" -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== dependencies: - call-bind "^1.0.6" + call-bound "^1.0.2" es-errors "^1.3.0" is-data-view "^1.0.1" @@ -4684,9 +4597,9 @@ domhandler@^5.0.2, domhandler@^5.0.3: domelementtype "^2.3.0" domutils@^3.0.1, domutils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" - integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + version "3.2.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.1.tgz#b39f4c390a1ae6f6a2c56a5f5a16d6438b6bce28" + integrity sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw== dependencies: dom-serializer "^2.0.0" domelementtype "^2.3.0" @@ -4705,6 +4618,15 @@ dotenv@^16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== +dunder-proto@^1.0.0, dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -4719,11 +4641,16 @@ easy-table@1.2.0: optionalDependencies: wcwidth "^1.0.1" -electron-to-chromium@^1.5.41, electron-to-chromium@^1.5.73: +electron-to-chromium@^1.5.41: version "1.5.73" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz#f32956ce40947fa3c8606726a96cd8fb5bb5f720" integrity sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg== +electron-to-chromium@^1.5.73: + version "1.5.76" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz#db20295c5061b68f07c8ea4dfcbd701485d94a3d" + integrity sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -4743,9 +4670,9 @@ encoding-sniffer@^0.2.0: whatwg-encoding "^3.1.1" enhanced-resolve@^5.17.1: - version "5.17.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" - integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + version "5.18.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz#91eb1db193896b9801251eeff1c6980278b1e404" + integrity sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -4772,89 +4699,94 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== +es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9: + version "1.23.9" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.9.tgz#5b45994b7de78dada5c1bebf1379646b32b9d606" + integrity sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA== dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" + call-bind "^1.0.8" + call-bound "^1.0.3" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" es-errors "^1.3.0" es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.2.7" + get-proto "^1.0.0" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" + has-proto "^1.2.0" + has-symbols "^1.1.0" hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" + is-data-view "^1.0.2" + is-regex "^1.2.1" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.0" + math-intrinsics "^1.1.0" + object-inspect "^1.13.3" object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.3" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.18" -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== -es-errors@^1.2.1, es-errors@^1.3.0: +es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-iterator-helpers@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz#f6d745d342aea214fe09497e7152170dc333a7a6" - integrity sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw== +es-iterator-helpers@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75" + integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" define-properties "^1.2.1" - es-abstract "^1.23.3" + es-abstract "^1.23.6" es-errors "^1.3.0" es-set-tostringtag "^2.0.3" function-bind "^1.1.2" - get-intrinsic "^1.2.4" + get-intrinsic "^1.2.6" globalthis "^1.0.4" + gopd "^1.2.0" has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - iterator.prototype "^1.1.3" - safe-array-concat "^1.1.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + iterator.prototype "^1.1.4" + safe-array-concat "^1.1.3" es-module-lexer@^1.5.4: version "1.5.4" @@ -4868,14 +4800,15 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== +es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== dependencies: - get-intrinsic "^1.2.4" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" has-tostringtag "^1.0.2" - hasown "^2.0.1" + hasown "^2.0.2" es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: version "1.0.2" @@ -4884,14 +4817,14 @@ es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: dependencies: hasown "^2.0.0" -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" esbuild@^0.21.3: version "0.21.5" @@ -4922,65 +4855,36 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" -esbuild@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.0.tgz#de06002d48424d9fdb7eb52dbe8e95927f852599" - integrity sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA== +esbuild@^0.24.0, esbuild@^0.24.2: + version "0.24.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d" + integrity sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA== optionalDependencies: - "@esbuild/aix-ppc64" "0.23.0" - "@esbuild/android-arm" "0.23.0" - "@esbuild/android-arm64" "0.23.0" - "@esbuild/android-x64" "0.23.0" - "@esbuild/darwin-arm64" "0.23.0" - "@esbuild/darwin-x64" "0.23.0" - "@esbuild/freebsd-arm64" "0.23.0" - "@esbuild/freebsd-x64" "0.23.0" - "@esbuild/linux-arm" "0.23.0" - "@esbuild/linux-arm64" "0.23.0" - "@esbuild/linux-ia32" "0.23.0" - "@esbuild/linux-loong64" "0.23.0" - "@esbuild/linux-mips64el" "0.23.0" - "@esbuild/linux-ppc64" "0.23.0" - "@esbuild/linux-riscv64" "0.23.0" - "@esbuild/linux-s390x" "0.23.0" - "@esbuild/linux-x64" "0.23.0" - "@esbuild/netbsd-x64" "0.23.0" - "@esbuild/openbsd-arm64" "0.23.0" - "@esbuild/openbsd-x64" "0.23.0" - "@esbuild/sunos-x64" "0.23.0" - "@esbuild/win32-arm64" "0.23.0" - "@esbuild/win32-ia32" "0.23.0" - "@esbuild/win32-x64" "0.23.0" - -esbuild@^0.24.0: - version "0.24.0" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.0.tgz#f2d470596885fcb2e91c21eb3da3b3c89c0b55e7" - integrity sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ== - optionalDependencies: - "@esbuild/aix-ppc64" "0.24.0" - "@esbuild/android-arm" "0.24.0" - "@esbuild/android-arm64" "0.24.0" - "@esbuild/android-x64" "0.24.0" - "@esbuild/darwin-arm64" "0.24.0" - "@esbuild/darwin-x64" "0.24.0" - "@esbuild/freebsd-arm64" "0.24.0" - "@esbuild/freebsd-x64" "0.24.0" - "@esbuild/linux-arm" "0.24.0" - "@esbuild/linux-arm64" "0.24.0" - "@esbuild/linux-ia32" "0.24.0" - "@esbuild/linux-loong64" "0.24.0" - "@esbuild/linux-mips64el" "0.24.0" - "@esbuild/linux-ppc64" "0.24.0" - "@esbuild/linux-riscv64" "0.24.0" - "@esbuild/linux-s390x" "0.24.0" - "@esbuild/linux-x64" "0.24.0" - "@esbuild/netbsd-x64" "0.24.0" - "@esbuild/openbsd-arm64" "0.24.0" - "@esbuild/openbsd-x64" "0.24.0" - "@esbuild/sunos-x64" "0.24.0" - "@esbuild/win32-arm64" "0.24.0" - "@esbuild/win32-ia32" "0.24.0" - "@esbuild/win32-x64" "0.24.0" + "@esbuild/aix-ppc64" "0.24.2" + "@esbuild/android-arm" "0.24.2" + "@esbuild/android-arm64" "0.24.2" + "@esbuild/android-x64" "0.24.2" + "@esbuild/darwin-arm64" "0.24.2" + "@esbuild/darwin-x64" "0.24.2" + "@esbuild/freebsd-arm64" "0.24.2" + "@esbuild/freebsd-x64" "0.24.2" + "@esbuild/linux-arm" "0.24.2" + "@esbuild/linux-arm64" "0.24.2" + "@esbuild/linux-ia32" "0.24.2" + "@esbuild/linux-loong64" "0.24.2" + "@esbuild/linux-mips64el" "0.24.2" + "@esbuild/linux-ppc64" "0.24.2" + "@esbuild/linux-riscv64" "0.24.2" + "@esbuild/linux-s390x" "0.24.2" + "@esbuild/linux-x64" "0.24.2" + "@esbuild/netbsd-arm64" "0.24.2" + "@esbuild/netbsd-x64" "0.24.2" + "@esbuild/openbsd-arm64" "0.24.2" + "@esbuild/openbsd-x64" "0.24.2" + "@esbuild/sunos-x64" "0.24.2" + "@esbuild/win32-arm64" "0.24.2" + "@esbuild/win32-ia32" "0.24.2" + "@esbuild/win32-x64" "0.24.2" escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" @@ -5094,27 +4998,27 @@ eslint-plugin-react-hooks@^5.0.0: integrity sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw== eslint-plugin-react@^7.29.4: - version "7.37.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz#cd0935987876ba2900df2f58339f6d92305acc7a" - integrity sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w== + version "7.37.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz#567549e9251533975c4ea9706f986c3a64832031" + integrity sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA== dependencies: array-includes "^3.1.8" array.prototype.findlast "^1.2.5" - array.prototype.flatmap "^1.3.2" + array.prototype.flatmap "^1.3.3" array.prototype.tosorted "^1.1.4" doctrine "^2.1.0" - es-iterator-helpers "^1.1.0" + es-iterator-helpers "^1.2.1" estraverse "^5.3.0" hasown "^2.0.2" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" object.entries "^1.1.8" object.fromentries "^2.0.8" - object.values "^1.2.0" + object.values "^1.2.1" prop-types "^15.8.1" resolve "^2.0.0-next.5" semver "^6.3.1" - string.prototype.matchall "^4.0.11" + string.prototype.matchall "^4.0.12" string.prototype.repeat "^1.0.0" eslint-plugin-rxjs@^5.0.3: @@ -5294,7 +5198,7 @@ fast-fifo@^1.3.2: resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== -fast-glob@^3.2.9, fast-glob@^3.3.2: +fast-glob@^3.2.9: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -5305,6 +5209,17 @@ fast-glob@^3.2.9, fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -5316,9 +5231,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.13.0, fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + version "1.18.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.18.0.tgz#d631d7e25faffea81887fe5ea8c9010e1b36fee0" + integrity sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw== dependencies: reusify "^1.0.4" @@ -5400,7 +5315,7 @@ fraction.js@^4.3.7: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== -fs-extra@^11.1.0: +fs-extra@^11.2.0: version "11.2.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== @@ -5463,15 +5378,17 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" functions-have-names@^1.2.3: version "1.2.3" @@ -5488,30 +5405,43 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044" + integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA== dependencies: + call-bind-apply-helpers "^1.0.1" + es-define-property "^1.0.1" es-errors "^1.3.0" + es-object-atoms "^1.0.0" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + get-proto "^1.0.0" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" get-nonce@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== +get-proto@^1.0.0, get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: - call-bind "^1.0.5" + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" es-errors "^1.3.0" - get-intrinsic "^1.2.4" + get-intrinsic "^1.2.6" glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" @@ -5597,7 +5527,7 @@ globals@^15.9.0: resolved "https://registry.yarnpkg.com/globals/-/globals-15.12.0.tgz#1811872883ad8f41055b61457a130221297de5b5" integrity sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ== -globalthis@^1.0.3, globalthis@^1.0.4: +globalthis@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== @@ -5617,12 +5547,10 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.8: version "4.2.11" @@ -5641,10 +5569,10 @@ gulp-sort@^2.0.0: dependencies: through2 "^2.0.1" -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== has-flag@^3.0.0: version "3.0.0" @@ -5663,24 +5591,26 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: dependencies: es-define-property "^1.0.0" -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: +has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: +hasown@^2.0.0, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -5787,35 +5717,42 @@ i18next-browser-languagedetector@^8.0.0: "@babel/runtime" "^7.23.2" i18next-parser@^9.0.0: - version "9.0.2" - resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-9.0.2.tgz#f9d627422d33c352967556c8724975d58f1f5a95" - integrity sha512-Q1yTZljBp1DcVAQD7LxduEqFRpjIeZc+5VnQ+gU8qG9WvY3U5rqK0IVONRWNtngh3orb197bfy1Sz4wlwcplxg== + version "9.1.0" + resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-9.1.0.tgz#e1cc3f42c4b2e1b1013f1ad30e69f076a280af72" + integrity sha512-HuuTdZ7VoDHtBnB8x9Vwt/DT4XgflGkRgIDhmjxy5Aa6YYzaRpkMqIcR3GDO+LVX09CYG9K+T10x+JeZfsl4sw== dependencies: - "@babel/runtime" "^7.23.2" + "@babel/runtime" "^7.25.0" broccoli-plugin "^4.0.7" cheerio "^1.0.0" - colors "1.4.0" - commander "~12.1.0" + colors "^1.4.0" + commander "^12.1.0" eol "^0.9.1" - esbuild "^0.23.0" - fs-extra "^11.1.0" + esbuild "^0.24.0" + fs-extra "^11.2.0" gulp-sort "^2.0.0" - i18next "^23.5.1" - js-yaml "4.1.0" - lilconfig "^3.0.0" - rsvp "^4.8.2" + i18next "^23.5.1 || ^24.2.0" + js-yaml "^4.1.0" + lilconfig "^3.1.2" + rsvp "^4.8.5" sort-keys "^5.0.0" typescript "^5.0.4" - vinyl "~3.0.0" + vinyl "^3.0.0" vinyl-fs "^4.0.0" -i18next@^23.0.0, i18next@^23.5.1: +i18next@^23.0.0: version "23.16.8" resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.16.8.tgz#3ae1373d344c2393f465556f394aba5a9233b93a" integrity sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg== dependencies: "@babel/runtime" "^7.23.2" +"i18next@^23.5.1 || ^24.2.0": + version "24.2.0" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-24.2.0.tgz#344c15a6f3b43d02aed78e2b08532e5b170951b4" + integrity sha512-ArJJTS1lV6lgKH7yEf4EpgNZ7+THl7bsGxxougPYiXRTJ/Fe1j08/TBpV9QsXCIYVfdE/HWG/xLezJ5DOlfBOA== + dependencies: + "@babel/runtime" "^7.23.2" + iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -5869,27 +5806,28 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== dependencies: es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" + hasown "^2.0.2" + side-channel "^1.1.0" interpret@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" is-arrayish@^0.2.1: version "0.2.1" @@ -5897,18 +5835,21 @@ is-arrayish@^0.2.1: integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-async-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.0.tgz#1d1080612c493608e93168fc4458c245074c06a6" + integrity sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== dependencies: - has-bigints "^1.0.1" + has-bigints "^1.0.2" is-binary-path@~2.1.0: version "2.1.0" @@ -5917,13 +5858,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== +is-boolean-object@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.1.tgz#c20d0c654be05da4fbc23c562635c019e93daf89" + integrity sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng== dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + call-bound "^1.0.2" + has-tostringtag "^1.0.2" is-builtin-module@^3.2.1: version "3.2.1" @@ -5932,43 +5873,53 @@ is-builtin-module@^3.2.1: dependencies: builtin-modules "^3.3.0" -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: +is-callable@^1.1.3, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.13.0, is-core-module@^2.15.1: +is-core-module@^2.13.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-core-module@^2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== dependencies: hasown "^2.0.2" -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" is-typed-array "^1.1.13" -is-date-object@^1.0.1, is-date-object@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.2" + has-tostringtag "^1.0.2" is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-finalizationregistry@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" - integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== dependencies: - call-bind "^1.0.2" + call-bound "^1.0.3" is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -5976,11 +5927,14 @@ is-fullwidth-code-point@^3.0.0: integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-function@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" @@ -5999,17 +5953,13 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug== -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.3" + has-tostringtag "^1.0.2" is-number@^7.0.0: version "7.0.0" @@ -6031,51 +5981,56 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" is-set@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== dependencies: - call-bind "^1.0.7" + call-bound "^1.0.3" is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== +is-string@^1.0.7, is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.3" + has-tostringtag "^1.0.2" -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== dependencies: - has-symbols "^1.0.2" + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== dependencies: - which-typed-array "^1.1.14" + which-typed-array "^1.1.16" is-valid-glob@^1.0.0: version "1.0.0" @@ -6087,20 +6042,20 @@ is-weakmap@^2.0.2: resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== +is-weakref@^1.0.2, is-weakref@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.0.tgz#47e3472ae95a63fa9cf25660bcf0c181c39770ef" + integrity sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q== dependencies: - call-bind "^1.0.2" + call-bound "^1.0.2" is-weakset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" - integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" isarray@0.0.1: version "0.0.1" @@ -6153,16 +6108,17 @@ istanbul-reports@^3.1.7: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterator.prototype@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.3.tgz#016c2abe0be3bbdb8319852884f60908ac62bf9c" - integrity sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ== +iterator.prototype@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" + integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== dependencies: - define-properties "^1.2.1" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - reflect.getprototypeof "^1.0.4" - set-function-name "^2.0.1" + define-data-property "^1.1.4" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + get-proto "^1.0.0" + has-symbols "^1.1.0" + set-function-name "^2.0.2" jackspeak@^3.1.2: version "3.4.3" @@ -6174,16 +6130,16 @@ jackspeak@^3.1.2: "@pkgjs/parseargs" "^0.11.0" jiti@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.1.tgz#4de9766ccbfa941d9b6390d2b159a4b295a52e6b" - integrity sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g== + version "2.4.2" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" + integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.1.0, js-yaml@^4.1.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -6303,9 +6259,9 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== knip@^5.27.2: - version "5.40.0" - resolved "https://registry.yarnpkg.com/knip/-/knip-5.40.0.tgz#6da9113d9d0c696fc3e5dc3f3a281db57b4b828a" - integrity sha512-EzBfQDz4YBzYnMLueWnaaVr15mneqZs1c3RanttciuVuRcodlNjzAmR2nch/khlRdVABAxAdMGFxfSvhvcH1NA== + version "5.41.1" + resolved "https://registry.yarnpkg.com/knip/-/knip-5.41.1.tgz#b6e27186d38e6bccd2ef8346294e78d13322f1cd" + integrity sha512-yNpCCe2REU7U3VRvMASnXSEtfEC2HmOoDW9Vp9teQ9FktJYnuagvSZD3xWq8Ru7sPABkmvbC5TVWuMzIaeADNA== dependencies: "@nodelib/fs.walk" "1.2.8" "@snyk/github-codeowners" "1.1.0" @@ -6349,10 +6305,10 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lilconfig@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" - integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== +lilconfig@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== lines-and-columns@^1.1.6: version "1.2.4" @@ -6497,6 +6453,11 @@ matcher-collection@^2.0.0: "@types/minimatch" "^3.0.3" minimatch "^3.0.2" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + matrix-events-sdk@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" @@ -6535,7 +6496,7 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -6695,24 +6656,26 @@ object-assign@^4.1.0, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" - integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== +object-inspect@^1.13.3: + version "1.13.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" + integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.4, object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== +object.assign@^4.1.4, object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== dependencies: - call-bind "^1.0.5" + call-bind "^1.0.8" + call-bound "^1.0.3" define-properties "^1.2.1" - has-symbols "^1.0.3" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" object-keys "^1.1.1" object.entries@^1.1.8: @@ -6743,7 +6706,17 @@ object.groupby@^1.0.3: define-properties "^1.2.1" es-abstract "^1.23.2" -object.values@^1.1.6, object.values@^1.2.0: +object.values@^1.1.6, object.values@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" + integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.values@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== @@ -6783,6 +6756,15 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -6864,11 +6846,11 @@ parse-ms@^4.0.0: integrity sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw== parse5-htmlparser2-tree-adapter@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" - integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + version "7.1.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== dependencies: - domhandler "^5.0.2" + domhandler "^5.0.3" parse5 "^7.0.0" parse5-parser-stream@^7.1.2: @@ -6879,11 +6861,11 @@ parse5-parser-stream@^7.1.2: parse5 "^7.0.0" parse5@^7.0.0, parse5@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== dependencies: - entities "^4.4.0" + entities "^4.5.0" path-exists@^4.0.0: version "4.0.0" @@ -6984,12 +6966,12 @@ postcss-clamp@^4.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-color-functional-notation@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.6.tgz#d74c1e2294b72287eb9af079c04b7ddeff7ec5b3" - integrity sha512-wLXvm8RmLs14Z2nVpB4CWlnvaWPRcOZFltJSlcbYwSJ1EDZKsKDhPKIMecCnuU054KSmlmubkqczmm6qBPCBhA== +postcss-color-functional-notation@^7.0.7: + version "7.0.7" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.7.tgz#c5362df010926f902ce4e7fb3da2a46cff175d1b" + integrity sha512-EZvAHsvyASX63vXnyXOIynkxhaHRSsdb7z6yiXKIovGXAolW4cMZ3qoh7k3VdTsLBS6VGdksGfIo3r6+waLoOw== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/postcss-progressive-custom-properties" "^4.0.0" @@ -7090,12 +7072,12 @@ postcss-image-set-function@^7.0.0: "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" -postcss-lab-function@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-7.0.6.tgz#3121800fc7939ed1d9a1e87abeb33c407151252c" - integrity sha512-HPwvsoK7C949vBZ+eMyvH2cQeMr3UREoHvbtra76/UhDuiViZH6pir+z71UaJQohd7VDSVUdR6TkWYKExEc9aQ== +postcss-lab-function@^7.0.7: + version "7.0.7" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-7.0.7.tgz#9c87c21ce5132c55824190b75d7d7adede9c2fac" + integrity sha512-+ONj2bpOQfsCKZE2T9VGMyVVdGcGUpr7u3SVfvkJlvhTRmDCfY25k4Jc8fubB9DclAPR4+w8uVtDZmdRgdAHig== dependencies: - "@csstools/css-color-parser" "^3.0.6" + "@csstools/css-color-parser" "^3.0.7" "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" "@csstools/postcss-progressive-custom-properties" "^4.0.0" @@ -7142,19 +7124,19 @@ postcss-place@^10.0.0: postcss-value-parser "^4.2.0" postcss-preset-env@^10.0.0: - version "10.1.2" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.1.2.tgz#ea9c25d92045ef06edd78f9945d2586107aab3e3" - integrity sha512-OqUBZ9ByVfngWhMNuBEMy52Izj07oIFA6K/EOGBlaSv+P12MiE1+S2cqXtS1VuW82demQ/Tzc7typYk3uHunkA== + version "10.1.3" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.1.3.tgz#7d07adef2237a643162e751b00eb1e339aa3b82e" + integrity sha512-9qzVhcMFU/MnwYHyYpJz4JhGku/4+xEiPTmhn0hj3IxnUYlEF9vbh7OC1KoLAnenS6Fgg43TKNp9xcuMeAi4Zw== dependencies: "@csstools/postcss-cascade-layers" "^5.0.1" - "@csstools/postcss-color-function" "^4.0.6" - "@csstools/postcss-color-mix-function" "^3.0.6" + "@csstools/postcss-color-function" "^4.0.7" + "@csstools/postcss-color-mix-function" "^3.0.7" "@csstools/postcss-content-alt-text" "^2.0.4" - "@csstools/postcss-exponential-functions" "^2.0.5" + "@csstools/postcss-exponential-functions" "^2.0.6" "@csstools/postcss-font-format-keywords" "^4.0.0" - "@csstools/postcss-gamut-mapping" "^2.0.6" - "@csstools/postcss-gradients-interpolation-method" "^5.0.6" - "@csstools/postcss-hwb-function" "^4.0.6" + "@csstools/postcss-gamut-mapping" "^2.0.7" + "@csstools/postcss-gradients-interpolation-method" "^5.0.7" + "@csstools/postcss-hwb-function" "^4.0.7" "@csstools/postcss-ic-unit" "^4.0.0" "@csstools/postcss-initial" "^2.0.0" "@csstools/postcss-is-pseudo-class" "^5.0.1" @@ -7164,19 +7146,19 @@ postcss-preset-env@^10.0.0: "@csstools/postcss-logical-overscroll-behavior" "^2.0.0" "@csstools/postcss-logical-resize" "^3.0.0" "@csstools/postcss-logical-viewport-units" "^3.0.3" - "@csstools/postcss-media-minmax" "^2.0.5" + "@csstools/postcss-media-minmax" "^2.0.6" "@csstools/postcss-media-queries-aspect-ratio-number-values" "^3.0.4" "@csstools/postcss-nested-calc" "^4.0.0" "@csstools/postcss-normalize-display-values" "^4.0.0" - "@csstools/postcss-oklab-function" "^4.0.6" + "@csstools/postcss-oklab-function" "^4.0.7" "@csstools/postcss-progressive-custom-properties" "^4.0.0" - "@csstools/postcss-random-function" "^1.0.1" - "@csstools/postcss-relative-color-syntax" "^3.0.6" + "@csstools/postcss-random-function" "^1.0.2" + "@csstools/postcss-relative-color-syntax" "^3.0.7" "@csstools/postcss-scope-pseudo-class" "^4.0.1" - "@csstools/postcss-sign-functions" "^1.1.0" - "@csstools/postcss-stepped-value-functions" "^4.0.5" + "@csstools/postcss-sign-functions" "^1.1.1" + "@csstools/postcss-stepped-value-functions" "^4.0.6" "@csstools/postcss-text-decoration-shorthand" "^4.0.1" - "@csstools/postcss-trigonometric-functions" "^4.0.5" + "@csstools/postcss-trigonometric-functions" "^4.0.6" "@csstools/postcss-unset-value" "^4.0.0" autoprefixer "^10.4.19" browserslist "^4.23.1" @@ -7186,7 +7168,7 @@ postcss-preset-env@^10.0.0: cssdb "^8.2.3" postcss-attribute-case-insensitive "^7.0.1" postcss-clamp "^4.1.0" - postcss-color-functional-notation "^7.0.6" + postcss-color-functional-notation "^7.0.7" postcss-color-hex-alpha "^10.0.0" postcss-color-rebeccapurple "^10.0.0" postcss-custom-media "^11.0.5" @@ -7199,7 +7181,7 @@ postcss-preset-env@^10.0.0: postcss-font-variant "^5.0.0" postcss-gap-properties "^6.0.0" postcss-image-set-function "^7.0.0" - postcss-lab-function "^7.0.6" + postcss-lab-function "^7.0.7" postcss-logical "^8.0.0" postcss-nesting "^13.0.1" postcss-opacity-percentage "^3.0.0" @@ -7395,9 +7377,9 @@ react-error-boundary@^3.1.0: "@babel/runtime" "^7.12.5" react-i18next@^15.0.0: - version "15.2.0" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.2.0.tgz#6b51650e1e93eb4d235a4d533fcf61b3bbf4ea10" - integrity sha512-iJNc8111EaDtVTVMKigvBtPHyrJV+KblWG73cUxqp+WmJCcwkzhWNFXmkAD5pwP2Z4woeDj/oXDdbjDsb3Gutg== + version "15.4.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.4.0.tgz#87c755fb6d7a567eec134e4759b022a0baacb19e" + integrity sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw== dependencies: "@babel/runtime" "^7.25.0" html-parse-stringify "^3.0.1" @@ -7417,7 +7399,7 @@ react-refresh@^0.14.2: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== -react-remove-scroll-bar@^2.3.6, react-remove-scroll-bar@^2.3.7: +react-remove-scroll-bar@^2.3.7: version "2.3.8" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== @@ -7425,17 +7407,6 @@ react-remove-scroll-bar@^2.3.6, react-remove-scroll-bar@^2.3.7: react-style-singleton "^2.2.2" tslib "^2.0.0" -react-remove-scroll@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz#fb03a0845d7768a4f1519a99fdb84983b793dc07" - integrity sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ== - dependencies: - react-remove-scroll-bar "^2.3.6" - react-style-singleton "^2.2.1" - tslib "^2.1.0" - use-callback-ref "^1.3.0" - use-sidecar "^1.1.2" - react-remove-scroll@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz#2518d2c5112e71ea8928f1082a58459b5c7a2a97" @@ -7572,18 +7543,19 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -reflect.getprototypeof@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" - integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" define-properties "^1.2.1" - es-abstract "^1.23.1" + es-abstract "^1.23.9" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" - which-builtin-type "^1.1.3" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" regenerate-unicode-properties@^10.2.0: version "10.2.0" @@ -7619,14 +7591,16 @@ regexp-tree@^0.1.27: resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== -regexp.prototype.flags@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42" - integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ== +regexp.prototype.flags@^1.5.3: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" define-properties "^1.2.1" es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" set-function-name "^2.0.2" regexpu-core@^6.1.1: @@ -7772,31 +7746,31 @@ rollup@^4.20.0: fsevents "~2.3.2" rollup@^4.23.0: - version "4.28.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.28.1.tgz#7718ba34d62b449dfc49adbfd2f312b4fe0df4de" - integrity sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg== + version "4.29.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.29.2.tgz#ff1555fd27fc20599a9b8f90527f0f43a1738e7f" + integrity sha512-tJXpsEkzsEzyAKIaB3qv3IuvTVcTN7qBw1jL4SPPXM3vzDrJgiLGFY6+HodgFaUHAJ2RYJ94zV5MKRJCoQzQeA== dependencies: "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.28.1" - "@rollup/rollup-android-arm64" "4.28.1" - "@rollup/rollup-darwin-arm64" "4.28.1" - "@rollup/rollup-darwin-x64" "4.28.1" - "@rollup/rollup-freebsd-arm64" "4.28.1" - "@rollup/rollup-freebsd-x64" "4.28.1" - "@rollup/rollup-linux-arm-gnueabihf" "4.28.1" - "@rollup/rollup-linux-arm-musleabihf" "4.28.1" - "@rollup/rollup-linux-arm64-gnu" "4.28.1" - "@rollup/rollup-linux-arm64-musl" "4.28.1" - "@rollup/rollup-linux-loongarch64-gnu" "4.28.1" - "@rollup/rollup-linux-powerpc64le-gnu" "4.28.1" - "@rollup/rollup-linux-riscv64-gnu" "4.28.1" - "@rollup/rollup-linux-s390x-gnu" "4.28.1" - "@rollup/rollup-linux-x64-gnu" "4.28.1" - "@rollup/rollup-linux-x64-musl" "4.28.1" - "@rollup/rollup-win32-arm64-msvc" "4.28.1" - "@rollup/rollup-win32-ia32-msvc" "4.28.1" - "@rollup/rollup-win32-x64-msvc" "4.28.1" + "@rollup/rollup-android-arm-eabi" "4.29.2" + "@rollup/rollup-android-arm64" "4.29.2" + "@rollup/rollup-darwin-arm64" "4.29.2" + "@rollup/rollup-darwin-x64" "4.29.2" + "@rollup/rollup-freebsd-arm64" "4.29.2" + "@rollup/rollup-freebsd-x64" "4.29.2" + "@rollup/rollup-linux-arm-gnueabihf" "4.29.2" + "@rollup/rollup-linux-arm-musleabihf" "4.29.2" + "@rollup/rollup-linux-arm64-gnu" "4.29.2" + "@rollup/rollup-linux-arm64-musl" "4.29.2" + "@rollup/rollup-linux-loongarch64-gnu" "4.29.2" + "@rollup/rollup-linux-powerpc64le-gnu" "4.29.2" + "@rollup/rollup-linux-riscv64-gnu" "4.29.2" + "@rollup/rollup-linux-s390x-gnu" "4.29.2" + "@rollup/rollup-linux-x64-gnu" "4.29.2" + "@rollup/rollup-linux-x64-musl" "4.29.2" + "@rollup/rollup-win32-arm64-msvc" "4.29.2" + "@rollup/rollup-win32-ia32-msvc" "4.29.2" + "@rollup/rollup-win32-x64-msvc" "4.29.2" fsevents "~2.3.2" rrweb-cssom@^0.7.1: @@ -7804,7 +7778,7 @@ rrweb-cssom@^0.7.1: resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== -rsvp@^4.8.2: +rsvp@^4.8.5: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== @@ -7841,14 +7815,15 @@ rxjs@7.8.1, rxjs@^7.5.2, rxjs@^7.8.1: dependencies: tslib "^2.1.0" -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" isarray "^2.0.5" safe-buffer@~5.1.0, safe-buffer@~5.1.1: @@ -7861,14 +7836,22 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== dependencies: - call-bind "^1.0.6" es-errors "^1.3.0" - is-regex "^1.1.4" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" @@ -7876,9 +7859,9 @@ safe-regex-test@^1.0.3: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass@^1.42.1: - version "1.83.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.0.tgz#e36842c0b88a94ed336fd16249b878a0541d536f" - integrity sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw== + version "1.83.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.1.tgz#dee1ab94b47a6f9993d3195d36f556bcbda64846" + integrity sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA== dependencies: chokidar "^4.0.0" immutable "^5.0.2" @@ -7930,7 +7913,7 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -set-function-length@^1.2.1: +set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== @@ -7942,7 +7925,7 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" -set-function-name@^2.0.1, set-function-name@^2.0.2: +set-function-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== @@ -7952,6 +7935,15 @@ set-function-name@^2.0.1, set-function-name@^2.0.2: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -7973,15 +7965,45 @@ shelljs@0.8.4: interpret "^1.0.0" rechoir "^0.6.2" -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: - call-bind "^1.0.7" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" siginfo@^2.0.0: version "2.0.0" @@ -8017,9 +8039,9 @@ snake-case@^3.0.4: tslib "^2.0.3" sort-keys@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-5.0.0.tgz#5d775f8ae93ecc29bc7312bbf3acac4e36e3c446" - integrity sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw== + version "5.1.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-5.1.0.tgz#50a3f3d1ad3c5a76d043e0aeeba7299241e9aa5c" + integrity sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ== dependencies: is-plain-obj "^4.0.0" @@ -8077,9 +8099,9 @@ stream-composer@^1.0.2: streamx "^2.13.2" streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.18.0.tgz#5bc1a51eb412a667ebfdcd4e6cf6a6fc65721ac7" - integrity sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ== + version "2.21.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.21.1.tgz#f02979d8395b6b637d08a589fb514498bed55845" + integrity sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw== dependencies: fast-fifo "^1.3.2" queue-tick "^1.0.1" @@ -8123,23 +8145,24 @@ string.prototype.includes@^2.0.1: define-properties "^1.2.1" es-abstract "^1.23.3" -string.prototype.matchall@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" - integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== +string.prototype.matchall@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" + integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" define-properties "^1.2.1" - es-abstract "^1.23.2" + es-abstract "^1.23.6" es-errors "^1.3.0" es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - regexp.prototype.flags "^1.5.2" + get-intrinsic "^1.2.6" + gopd "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + regexp.prototype.flags "^1.5.3" set-function-name "^2.0.2" - side-channel "^1.0.6" + side-channel "^1.1.0" string.prototype.repeat@^1.0.0: version "1.0.0" @@ -8149,22 +8172,26 @@ string.prototype.repeat@^1.0.0: define-properties "^1.1.3" es-abstract "^1.17.5" -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" define-properties "^1.2.1" - es-abstract "^1.23.0" + es-abstract "^1.23.5" es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== +string.prototype.trimend@^1.0.8, string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.2" define-properties "^1.2.1" es-object-atoms "^1.0.0" @@ -8298,9 +8325,9 @@ test-exclude@^7.0.1: minimatch "^9.0.4" text-decoder@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.1.1.tgz#5df9c224cebac4a7977720b9f083f9efa1aefde8" - integrity sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA== + version "1.2.3" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" + integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== dependencies: b4a "^1.6.4" @@ -8484,49 +8511,50 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== dependencies: - call-bind "^1.0.7" + call-bound "^1.0.3" es-errors "^1.3.0" - is-typed-array "^1.1.13" + is-typed-array "^1.1.14" -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== dependencies: available-typed-arrays "^1.0.7" - call-bind "^1.0.7" + call-bind "^1.0.8" for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== dependencies: call-bind "^1.0.7" for-each "^0.3.3" gopd "^1.0.1" - has-proto "^1.0.3" is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" typed-emitter@^2.1.0: version "2.1.0" @@ -8545,15 +8573,15 @@ typescript@^5.0.4, typescript@^5.1.6: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== dependencies: - call-bind "^1.0.2" + call-bound "^1.0.3" has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" underscore.string@~3.3.4: version "3.3.6" @@ -8581,9 +8609,9 @@ undici@^5.25.4: "@fastify/busboy" "^2.0.0" undici@^6.19.5: - version "6.19.7" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.19.7.tgz#7d4cf26dc689838aa8b6753a3c5c4288fc1e0216" - integrity sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A== + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.0.tgz#4b3d3afaef984e07b48e7620c34ed8a285ed4cd4" + integrity sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw== unhomoglyph@^1.0.6: version "1.0.6" @@ -8666,7 +8694,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -use-callback-ref@^1.3.0, use-callback-ref@^1.3.3: +use-callback-ref@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== @@ -8763,7 +8791,7 @@ vinyl-sourcemap@^2.0.0: vinyl "^3.0.0" vinyl-contents "^2.0.0" -vinyl@^3.0.0, vinyl@~3.0.0: +vinyl@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-3.0.0.tgz#11e14732bf56e2faa98ffde5157fe6c13259ff30" integrity sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g== @@ -8821,11 +8849,11 @@ vite@^5.0.0: fsevents "~2.3.3" vite@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.3.tgz#cc01f403e326a9fc1e064235df8a6de084c8a491" - integrity sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw== + version "6.0.7" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.7.tgz#f0f8c120733b04af52b4a1e3e7cb54eb851a799b" + integrity sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ== dependencies: - esbuild "^0.24.0" + esbuild "^0.24.2" postcss "^8.4.49" rollup "^4.23.0" optionalDependencies: @@ -8960,34 +8988,35 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" -which-builtin-type@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.4.tgz#592796260602fc3514a1b5ee7fa29319b72380c3" - integrity sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w== +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== dependencies: + call-bound "^1.0.2" function.prototype.name "^1.1.6" has-tostringtag "^1.0.2" is-async-function "^2.0.0" - is-date-object "^1.0.5" - is-finalizationregistry "^1.0.2" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" is-generator-function "^1.0.10" - is-regex "^1.1.4" + is-regex "^1.2.1" is-weakref "^1.0.2" isarray "^2.0.5" - which-boxed-primitive "^1.0.2" + which-boxed-primitive "^1.1.0" which-collection "^1.0.2" - which-typed-array "^1.1.15" + which-typed-array "^1.1.16" which-collection@^1.0.2: version "1.0.2" @@ -9004,15 +9033,16 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== +which-typed-array@^1.1.16, which-typed-array@^1.1.18: + version "1.1.18" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.18.tgz#df2389ebf3fbb246a71390e90730a9edb6ce17ad" + integrity sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA== dependencies: available-typed-arrays "^1.0.7" - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" for-each "^0.3.3" - gopd "^1.0.1" + gopd "^1.2.0" has-tostringtag "^1.0.2" which@^2.0.1, which@^2.0.2: From 46a82b2c390ab9a4954c283d97bdfd9a43312303 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:18:12 +0000 Subject: [PATCH 72/72] Update dependency @vector-im/compound-design-tokens to v3 (#2896) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ac693368..bc85b9c6 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "@use-gesture/react": "^10.2.11", - "@vector-im/compound-design-tokens": "^2.0.0", + "@vector-im/compound-design-tokens": "^3.0.0", "@vector-im/compound-web": "^7.2.0", "@vitejs/plugin-basic-ssl": "^1.0.1", "@vitejs/plugin-react": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index a74d404a..a555346d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3411,10 +3411,10 @@ dependencies: "@use-gesture/core" "10.3.1" -"@vector-im/compound-design-tokens@^2.0.0": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.3.tgz#8205ffb455a09d71a02d838f3dbb8503c4e6ec27" - integrity sha512-U4UF7MVguENf0lQnkU2a9p/3llTsLXzbzmFFOxi0h6ny2igNxZj/kROP/jXTxxV9xD4TNn3z098Bos4J/qJpBA== +"@vector-im/compound-design-tokens@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-3.0.1.tgz#ee9c6ca84946b17900cdda10715b69c4b4b29264" + integrity sha512-uwpMteMxfX1xRKM5xe6IcuHAGMWYFHAD/R0ANia6JYnP2Q2ytlqUDDFa/nqNqMLYdVytbZs3bJtOfuJxmUJfgg== "@vector-im/compound-web@^7.2.0": version "7.6.1"