diff --git a/.eslintrc.cjs b/.eslintrc.cjs index bedf1c68..31dccb6a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -37,6 +37,7 @@ module.exports = { "@typescript-eslint/promise-function-async": "error", "@typescript-eslint/require-await": "error", "@typescript-eslint/await-thenable": "error", + "rxjs/no-exposed-subjects": "error", }, settings: { react: { diff --git a/config/nginx.conf b/config/nginx.conf index f3f8140e..e6f6e6be 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -8,7 +8,7 @@ server { location / { try_files $uri $uri/ /index.html; - add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header Cache-Control "public, max-age=30, stale-while-revalidate=30"; } # assets can be cached because they have hashed filenames diff --git a/locales/de/app.json b/locales/de/app.json index 744fa792..7c465406 100644 --- a/locales/de/app.json +++ b/locales/de/app.json @@ -4,19 +4,18 @@ }, "action": { "close": "Schließen", - "close_search": "Suche beenden", "copy_link": "Link kopieren", "edit": "Bearbeiten", "go": "Los geht’s", "invite": "Einladen", "lower_hand": "Handmeldung zurücknehmen", "no": "Nein", - "open_search": "Suchen", "pick_reaction": "Reaktion auswählen", "raise_hand": "Handmeldung", - "raise_hand_or_send_reaction": "Handmeldung oder Reaktion senden", "register": "Registrieren", "remove": "Entfernen", + "show_less": "Weniger anzeigen", + "show_more": "Mehr anzeigen", "sign_in": "Anmelden", "sign_out": "Abmelden", "submit": "Absenden", @@ -62,7 +61,7 @@ "preferences": "Einstellungen", "profile": "Profil", "reaction": "Reaktion", - "search": "Suche", + "reactions": "Reaktionen", "settings": "Einstellungen", "something_went_wrong": "Etwas ist schief gelaufen", "unencrypted": "Nicht verschlüsselt", @@ -128,7 +127,6 @@ "rageshake_sending": "Senden …", "rageshake_sending_logs": "Sende Debug-Protokolle …", "rageshake_sent": "Danke!", - "reaction_search": "Reaktionen suchen...", "recaptcha_caption": "Diese Seite wird durch reCAPTCHA geschützt und es gelten Googles <2>Datenschutzerklärung und <6>Nutzungsbedingungen. <9>Mit einem Klick auf „Registrieren“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)", "recaptcha_dismissed": "Recaptcha abgelehnt", "recaptcha_not_loaded": "Recaptcha nicht geladen", @@ -163,8 +161,8 @@ "preferences_tab": { "reactions_play_sound_description": "Einen Soundeffekt abspielen, wenn jemand eine Reaktion sendet", "reactions_play_sound_label": "Reaktionstöne abspielen", - "reactions_show_description": "Reaktionen anzeigen", - "reactions_show_label": "Zeige eine Animation, wenn jemand eine Reaktion sendet.", + "reactions_show_description": "Zeige eine Animation, wenn jemand eine Reaktion sendet.", + "reactions_show_label": "Reaktionen anzeigen", "reactions_title": "Reaktionen" }, "preferences_tab_body": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden", diff --git a/locales/en-GB/app.json b/locales/en-GB/app.json index ca91d517..0b9142d2 100644 --- a/locales/en-GB/app.json +++ b/locales/en-GB/app.json @@ -4,19 +4,18 @@ }, "action": { "close": "Close", - "close_search": "Close search", "copy_link": "Copy link", "edit": "Edit", "go": "Go", "invite": "Invite", "lower_hand": "Lower hand", "no": "No", - "open_search": "Open search", "pick_reaction": "Pick reaction", "raise_hand": "Raise hand", - "raise_hand_or_send_reaction": "Raise hand or send reaction", "register": "Register", "remove": "Remove", + "show_less": "Show less", + "show_more": "Show more", "sign_in": "Sign in", "sign_out": "Sign out", "submit": "Submit", @@ -62,7 +61,7 @@ "preferences": "Preferences", "profile": "Profile", "reaction": "Reaction", - "search": "Search", + "reactions": "Reactions", "settings": "Settings", "something_went_wrong": "Something went wrong", "unencrypted": "Not encrypted", @@ -128,7 +127,6 @@ "rageshake_sending": "Sending…", "rageshake_sending_logs": "Sending debug logs…", "rageshake_sent": "Thanks!", - "reaction_search": "Search reactions…", "recaptcha_caption": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)", "recaptcha_dismissed": "Recaptcha dismissed", "recaptcha_not_loaded": "Recaptcha not loaded", @@ -163,8 +161,8 @@ "preferences_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 reactions", - "reactions_show_label": "Show an animation when anyone sends a reaction.", + "reactions_show_description": "Show an animation when anyone sends a reaction.", + "reactions_show_label": "Show reactions", "reactions_title": "Reactions" }, "preferences_tab_body": "Here you can configure extra options for an improved experience", diff --git a/public/index.html b/public/index.html index df1edfd9..e14d79be 100644 --- a/public/index.html +++ b/public/index.html @@ -3,6 +3,7 @@ + div > div { - padding-inline: var(--cpd-space-6x) !important; - padding-block: var(--cpd-space-6x) var(--cpd-space-8x) !important; +div.reactionPopupMenuRoot.reactionPopupMenuModal { + --overlay-top: 82vh; + width: fit-content; } -.reactionPopupMenu menu { - margin: 0; - padding: 0; - display: flex; - flex-wrap: wrap; - gap: var(--cpd-separator-spacing); +div.reactionPopupMenuRoot { + /* Center the drawer */ + --inset-inline: 30em; +} + +.reactionPopupMenuRoot > div { + width: fit-content; + max-width: 100vw; +} + +div.reactionPopupMenuRoot.reactionPopupMenuModal > div > div { + padding-inline: var(--cpd-space-6x); + padding-block: var(--cpd-space-6x); } .reactionPopupMenu section { height: fit-content; - margin-top: auto; - margin-bottom: auto; + flex: 1; + max-width: fit-content; } -.reactionPopupMenuItem { - list-style: none; +.reactionPopupMenu section.reactionsMenuSection { + margin: auto 0; + flex: auto; } .reactionsMenu { - min-height: 3em; + margin: 0; + padding: 0; + flex-grow: 1; + gap: var(--reaction-button-gap); + /* Height of 3 rows plus padding. */ + max-height: calc( + ((var(--reaction-button-fontsize) + var(--cpd-separator-spacing)) * 2) * 3 + ); + max-width: calc( + ((var(--reaction-button-fontsize) + var(--cpd-separator-spacing)) * 2) * 5 + ); + overflow-x: hidden; + overflow-y: auto; + list-style: none; + flex-wrap: wrap; + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: start; + align-items: auto; + align-content: start; + width: fit-content; +} + +.reactionsMenu > * { + flex: 0 0 auto; } .reactionButton { - padding: 1em; - font-size: 1.6em; - width: 1.4em; - height: 1.4em; + padding: var(--reaction-button-padding); border-radius: var(--cpd-radius-pill-effect); -} - -@media (max-width: 800px) { - .reactionButton { - padding: 1em; - font-size: 1em; - width: 1em; - height: 1em; - min-block-size: unset; - } + font-size: var(--reaction-button-fontsize); + min-block-size: unset; + border: none; + aspect-ratio: 1 / 1; + height: 100%; } .verticalSeperator { background-color: var(--cpd-color-gray-800); - width: 1px; + width: 2px; height: auto; margin-left: var(--cpd-separator-spacing); margin-right: var(--cpd-separator-spacing); } -.searchForm { - display: flex; - flex-direction: row; - gap: var(--cpd-separator-spacing); - margin-bottom: var(--cpd-space-3x); -} - -.searchForm > label { - flex: auto; -} - .alert { margin-bottom: var(--cpd-space-3x); animation: grow-in 200ms; diff --git a/src/button/ReactionToggleButton.test.tsx b/src/button/ReactionToggleButton.test.tsx index b13b74fa..a1498304 100644 --- a/src/button/ReactionToggleButton.test.tsx +++ b/src/button/ReactionToggleButton.test.tsx @@ -5,10 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { fireEvent, render } from "@testing-library/react"; -import { act } from "react"; +import { render } from "@testing-library/react"; import { expect, test } from "vitest"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; import { TooltipProvider } from "@vector-im/compound-web"; import { userEvent } from "@testing-library/user-event"; import { ReactNode } from "react"; @@ -30,18 +28,13 @@ const membership: Record = { function TestComponent({ rtcSession, - room, }: { rtcSession: MockRTCSession; - room: MockRoom; }): ReactNode { return ( - + ); @@ -52,9 +45,9 @@ test("Can open menu", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, container } = render( - , + , ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); + await user.click(getByLabelText("common.reactions")); expect(container).toMatchSnapshot(); }); @@ -63,9 +56,9 @@ test("Can raise hand", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, container } = render( - , + , ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); + await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.raise_hand")); expect(room.testSentEvents).toEqual([ [ @@ -88,10 +81,10 @@ test("Can lower hand", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, container } = render( - , + , ); const reactionEvent = room.testSendHandRaise(memberEventAlice, membership); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); + await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.lower_hand")); expect(room.testRedactedEvents).toEqual([[undefined, reactionEvent]]); expect(container).toMatchSnapshot(); @@ -102,9 +95,9 @@ test("Can react with emoji", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, getByText } = render( - , + , ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); + await user.click(getByLabelText("common.reactions")); await user.click(getByText("🐶")); expect(room.testSentEvents).toEqual([ [ @@ -122,17 +115,15 @@ test("Can react with emoji", async () => { ]); }); -test("Can search for and send emoji", async () => { +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( - , + , ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); - await user.click(getByLabelText("action.open_search")); - // Search should autofocus. - await user.keyboard("crickets"); + await user.click(getByLabelText("common.reactions")); + await user.click(getByLabelText("action.show_more")); expect(container).toMatchSnapshot(); await user.click(getByText("🦗")); @@ -152,63 +143,15 @@ test("Can search for and send emoji", async () => { ]); }); -test("Can search for and send emoji with the keyboard", async () => { - const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { getByLabelText, getByPlaceholderText, container } = render( - , - ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); - await user.click(getByLabelText("action.open_search")); - const searchField = getByPlaceholderText("reaction_search"); - // Search should autofocus. - await user.keyboard("crickets"); - expect(container).toMatchSnapshot(); - act(() => { - fireEvent.keyDown(searchField, { key: "Enter" }); - }); - expect(room.testSentEvents).toEqual([ - [ - undefined, - ElementCallReactionEventType, - { - "m.relates_to": { - event_id: memberEventAlice, - rel_type: "m.reference", - }, - name: "crickets", - emoji: "🦗", - }, - ], - ]); -}); - -test("Can close search", async () => { +test("Can close reaction dialog", async () => { const user = userEvent.setup(); const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, container } = render( - , + , ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); - await user.click(getByLabelText("action.open_search")); - await user.click(getByLabelText("action.close_search")); - expect(container).toMatchSnapshot(); -}); - -test("Can close search with the escape key", async () => { - const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { getByLabelText, container, getByPlaceholderText } = render( - , - ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); - await user.click(getByLabelText("action.open_search")); - const searchField = getByPlaceholderText("reaction_search"); - act(() => { - fireEvent.keyDown(searchField, { key: "Escape" }); - }); + await user.click(getByLabelText("common.reactions")); + await user.click(getByLabelText("action.show_more")); + await user.click(getByLabelText("action.show_less")); expect(container).toMatchSnapshot(); }); diff --git a/src/button/ReactionToggleButton.tsx b/src/button/ReactionToggleButton.tsx index 984d2f4c..b1d6ec3e 100644 --- a/src/button/ReactionToggleButton.tsx +++ b/src/button/ReactionToggleButton.tsx @@ -5,24 +5,16 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ +import { Button as CpdButton, Tooltip, Alert } from "@vector-im/compound-web"; import { - Button as CpdButton, - Tooltip, - Search, - Form, - Alert, -} from "@vector-im/compound-web"; -import { - SearchIcon, - CloseIcon, RaisedHandSolidIcon, - ReactionIcon, + ChevronDownIcon, + ChevronUpIcon, + ReactionSolidIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { - ChangeEventHandler, ComponentPropsWithoutRef, FC, - KeyboardEventHandler, ReactNode, useCallback, useEffect, @@ -31,19 +23,11 @@ import { } from "react"; import { useTranslation } from "react-i18next"; import { logger } from "matrix-js-sdk/src/logger"; -import { EventType, RelationType } from "matrix-js-sdk/src/matrix"; -import { MatrixClient } from "matrix-js-sdk/src/client"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import classNames from "classnames"; import { useReactions } from "../useReactions"; -import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships"; import styles from "./ReactionToggleButton.module.css"; -import { - ReactionOption, - ReactionSet, - ElementCallReactionEventType, -} from "../reactions"; +import { ReactionOption, ReactionSet, ReactionsRowSize } from "../reactions"; import { Modal } from "../Modal"; interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> { @@ -55,15 +39,14 @@ const InnerButton: FC = ({ raised, open, ...props }) => { const { t } = useTranslation(); return ( - + @@ -84,43 +67,11 @@ export function ReactionPopupMenu({ canReact: boolean; }): ReactNode { const { t } = useTranslation(); - const [searchText, setSearchText] = useState(""); - const [isSearching, setIsSearching] = useState(false); - const onSearch = useCallback>((ev) => { - ev.preventDefault(); - setSearchText(ev.target.value.trim().toLocaleLowerCase()); - }, []); + const [isFullyExpanded, setExpanded] = useState(false); const filteredReactionSet = useMemo( - () => - ReactionSet.filter( - (reaction) => - !isSearching || - (!!searchText && - (reaction.name.startsWith(searchText) || - reaction.alias?.some((a) => a.startsWith(searchText)))), - ).slice(0, 6), - [searchText, isSearching], - ); - - const onSearchKeyDown = useCallback>( - (ev) => { - if (ev.key === "Enter") { - ev.preventDefault(); - if (!canReact) { - return; - } - if (filteredReactionSet.length !== 1) { - return; - } - sendReaction(filteredReactionSet[0]); - setIsSearching(false); - } else if (ev.key === "Escape") { - ev.preventDefault(); - setIsSearching(false); - } - }, - [sendReaction, filteredReactionSet, canReact, setIsSearching], + () => (isFullyExpanded ? ReactionSet : ReactionSet.slice(0, 5)), + [isFullyExpanded], ); const label = isHandRaised ? t("action.lower_hand") : t("action.raise_hand"); return ( @@ -136,9 +87,10 @@ export function ReactionPopupMenu({ )}
- + toggleRaisedHand()} @@ -148,41 +100,33 @@ export function ReactionPopupMenu({
-
- {isSearching ? ( - <> - - - setIsSearching(false)} - /> - - - ) : null} - - {filteredReactionSet.map((reaction) => ( -
  • - +
    + + {filteredReactionSet.map((reaction, index) => ( +
  • + sendReaction(reaction)} + aria-keyshortcuts={ + index < ReactionsRowSize + ? (index + 1).toString() + : undefined + } > {reaction.emoji} @@ -191,73 +135,56 @@ export function ReactionPopupMenu({ ))}
  • - {!isSearching ? ( -
    -
  • - - setIsSearching(true)} - /> - -
  • -
    - ) : null} +
    + + setExpanded(!isFullyExpanded)} + /> + +
  • ); } interface ReactionToggleButtonProps extends ComponentPropsWithoutRef<"button"> { - rtcSession: MatrixRTCSession; - client: MatrixClient; + userId: string; } export function ReactionToggleButton({ - client, - rtcSession, + userId, ...props }: ReactionToggleButtonProps): ReactNode { const { t } = useTranslation(); - const { raisedHands, lowerHand, reactions } = useReactions(); + const { raisedHands, toggleRaisedHand, sendReaction, reactions } = + useReactions(); const [busy, setBusy] = useState(false); - const userId = client.getUserId()!; - const isHandRaised = !!raisedHands[userId]; - const memberships = useMatrixRTCSessionMemberships(rtcSession); const [showReactionsMenu, setShowReactionsMenu] = useState(false); const [errorText, setErrorText] = useState(); + const isHandRaised = !!raisedHands[userId]; + const canReact = !reactions[userId]; + useEffect(() => { // Clear whenever the reactions menu state changes. setErrorText(undefined); }, [showReactionsMenu]); - const canReact = !reactions[userId]; - const sendRelation = useCallback( async (reaction: ReactionOption) => { try { - const myMembership = memberships.find((m) => m.sender === userId); - if (!myMembership?.eventId) { - throw new Error("Cannot find own membership event"); - } - const parentEventId = myMembership.eventId; setBusy(true); - await client.sendEvent( - rtcSession.room.roomId, - ElementCallReactionEventType, - { - "m.relates_to": { - rel_type: RelationType.Reference, - event_id: parentEventId, - }, - emoji: reaction.emoji, - name: reaction.name, - }, - ); + await sendReaction(reaction); setErrorText(undefined); setShowReactionsMenu(false); } catch (ex) { @@ -267,59 +194,25 @@ export function ReactionToggleButton({ setBusy(false); } }, - [memberships, client, userId, rtcSession], + [sendReaction], ); - const toggleRaisedHand = useCallback(() => { - const raiseHand = async (): Promise => { - if (isHandRaised) { - try { - setBusy(true); - await lowerHand(); - setShowReactionsMenu(false); - } finally { - setBusy(false); - } - } else { - try { - const myMembership = memberships.find((m) => m.sender === userId); - if (!myMembership?.eventId) { - throw new Error("Cannot find own membership event"); - } - const parentEventId = myMembership.eventId; - setBusy(true); - const reaction = await client.sendEvent( - rtcSession.room.roomId, - EventType.Reaction, - { - "m.relates_to": { - rel_type: RelationType.Annotation, - event_id: parentEventId, - key: "🖐️", - }, - }, - ); - logger.debug("Sent raise hand event", reaction.event_id); - setErrorText(undefined); - setShowReactionsMenu(false); - } catch (ex) { - setErrorText(ex instanceof Error ? ex.message : "Unknown error"); - logger.error("Failed to raise hand", ex); - } finally { - setBusy(false); - } + const wrappedToggleRaisedHand = useCallback(() => { + const toggleHand = async (): Promise => { + try { + setBusy(true); + await toggleRaisedHand(); + setShowReactionsMenu(false); + } catch (ex) { + setErrorText(ex instanceof Error ? ex.message : "Unknown error"); + logger.error("Failed to raise/lower hand", ex); + } finally { + setBusy(false); } }; - void raiseHand(); - }, [ - client, - isHandRaised, - memberships, - lowerHand, - rtcSession.room.roomId, - userId, - ]); + void toggleHand(); + }, [toggleRaisedHand]); return ( <> @@ -335,14 +228,15 @@ export function ReactionToggleButton({ title={t("action.pick_reaction")} hideHeader classNameModal={styles.reactionPopupMenuModal} + className={styles.reactionPopupMenuRoot} onDismiss={() => setShowReactionsMenu(false)} > void sendRelation(reaction)} - toggleRaisedHand={toggleRaisedHand} + toggleRaisedHand={wrappedToggleRaisedHand} /> diff --git a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap index bee0bdb1..dd4227e1 100644 --- a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap +++ b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Can close search 1`] = ` +exports[`Can close reaction dialog 1`] = ` `; -exports[`Can close search with the escape key 1`] = ` -
    +exports[`Can fully expand emoji picker 1`] = ` + -`; - -exports[`Can search for and send emoji 1`] = ` - -`; - -exports[`Can search for and send emoji with the keyboard 1`] = ` -
    -
    +
    void; + setMicrophoneMuted?: (muted: boolean) => void; onButtonClick?: () => void; + sendReaction?: () => void; + toggleHandRaised?: () => void; } const TestComponent: FC = ({ - setMicrophoneMuted, + setMicrophoneMuted = (): void => {}, onButtonClick = (): void => {}, + sendReaction = (reaction: ReactionOption): void => {}, + toggleHandRaised = (): void => {}, }) => { const ref = useRef(null); useCallViewKeyboardShortcuts( @@ -32,6 +37,8 @@ const TestComponent: FC = ({ () => {}, () => {}, setMicrophoneMuted, + sendReaction, + toggleHandRaised, ); return (
    @@ -74,6 +81,28 @@ test("spacebar prioritizes pressing a button", async () => { expect(onClick).toBeCalled(); }); +test("reactions can be sent via keyboard presses", async () => { + const user = userEvent.setup(); + + const sendReaction = vi.fn(); + render(); + + for (let index = 1; index <= ReactionsRowSize; index++) { + await user.keyboard(index.toString()); + expect(sendReaction).toHaveBeenNthCalledWith(index, ReactionSet[index - 1]); + } +}); + +test("raised hand can be sent via keyboard presses", async () => { + const user = userEvent.setup(); + + const toggleHandRaised = vi.fn(); + render(); + await user.keyboard("h"); + + expect(toggleHandRaised).toHaveBeenCalledOnce(); +}); + test("unmuting happens in place of the default action", async () => { const user = userEvent.setup(); const defaultPrevented = vi.fn(); diff --git a/src/useCallViewKeyboardShortcuts.ts b/src/useCallViewKeyboardShortcuts.ts index d3e4f65e..7c27e1e2 100644 --- a/src/useCallViewKeyboardShortcuts.ts +++ b/src/useCallViewKeyboardShortcuts.ts @@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details. import { RefObject, useCallback, useMemo, useRef } from "react"; import { useEventTarget } from "./useEvents"; +import { ReactionOption, ReactionSet, ReactionsRowSize } from "./reactions"; /** * Determines whether focus is in the same part of the tree as the given @@ -18,11 +19,17 @@ const mayReceiveKeyEvents = (e: HTMLElement): boolean => { return focusedElement !== null && focusedElement.contains(e); }; +const KeyToReactionMap: Record = Object.fromEntries( + ReactionSet.slice(0, ReactionsRowSize).map((r, i) => [(i + 1).toString(), r]), +); + export function useCallViewKeyboardShortcuts( focusElement: RefObject, toggleMicrophoneMuted: () => void, toggleLocalVideoMuted: () => void, setMicrophoneMuted: (muted: boolean) => void, + sendReaction: (reaction: ReactionOption) => void, + toggleHandRaised: () => void, ): void { const spacebarHeld = useRef(false); @@ -49,6 +56,12 @@ export function useCallViewKeyboardShortcuts( spacebarHeld.current = true; setMicrophoneMuted(false); } + } else if (event.key === "h") { + event.preventDefault(); + toggleHandRaised(); + } else if (KeyToReactionMap[event.key]) { + event.preventDefault(); + sendReaction(KeyToReactionMap[event.key]); } }, [ @@ -56,6 +69,8 @@ export function useCallViewKeyboardShortcuts( toggleLocalVideoMuted, toggleMicrophoneMuted, setMicrophoneMuted, + sendReaction, + toggleHandRaised, ], ), // Because this is set on the window, to prevent shortcuts from activating diff --git a/src/useReactions.tsx b/src/useReactions.tsx index 8824f103..7195cfd0 100644 --- a/src/useReactions.tsx +++ b/src/useReactions.tsx @@ -40,7 +40,8 @@ interface ReactionsContextType { raisedHands: Record; supportsReactions: boolean; reactions: Record; - lowerHand: () => Promise; + toggleRaisedHand: () => Promise; + sendReaction: (reaction: ReactionOption) => Promise; } const ReactionsContext = createContext( @@ -104,7 +105,6 @@ export const ReactionsProvider = ({ ), [raisedHands], ); - const addRaisedHand = useCallback((userId: string, info: RaisedHandInfo) => { setRaisedHands((prevRaisedHands) => ({ ...prevRaisedHands, @@ -181,6 +181,11 @@ export const ReactionsProvider = ({ 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(); @@ -322,22 +327,67 @@ export const ReactionsProvider = ({ latestRaisedHands, ]); - const lowerHand = useCallback(async () => { - if (!myUserId || !raisedHands[myUserId]) { + const toggleRaisedHand = useCallback(async () => { + if (!myUserId) { return; } - const myReactionId = raisedHands[myUserId].reactionEventId; + const myReactionId = raisedHands[myUserId]?.reactionEventId; + if (!myReactionId) { - logger.warn(`Hand raised but no reaction event to redact!`); - return; + 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; + } } - 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); - } - }, [myUserId, raisedHands, rtcSession, room]); + }, [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.ts b/src/utils/test.ts index 2af6016c..771dd574 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -4,7 +4,7 @@ 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 } from "rxjs"; +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"; @@ -39,15 +39,23 @@ export interface OurRunHelpers extends RunHelpers { schedule: (marbles: string, actions: Record void>) => void; } +interface TestRunnerGlobal { + rxjsTestScheduler?: SchedulerLike; +} + /** * Run Observables with a scheduler that virtualizes time, for testing purposes. */ export function withTestScheduler( continuation: (helpers: OurRunHelpers) => void, ): void { - new TestScheduler((actual, expected) => { + const scheduler = new TestScheduler((actual, expected) => { expect(actual).deep.equals(expected); - }).run((helpers) => + }); + // we set the test scheduler as a global so that you can watch it in a debugger + // and get the frame number. e.g. `rxjsTestScheduler?.now()` + (global as unknown as TestRunnerGlobal).rxjsTestScheduler = scheduler; + scheduler.run((helpers) => continuation({ ...helpers, schedule(marbles, actions) { diff --git a/yarn.lock b/yarn.lock index 0e6b4355..20a94d6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -279,14 +279,14 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.4": +"@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.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": +"@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": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== @@ -995,10 +995,10 @@ resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.10.0.tgz#1a67ac889c2d464a3492b3e54c38f80517963b16" integrity sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag== -"@codecov/bundler-plugin-core@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.3.0.tgz#6b2e2eeabd20dc085f281dcb4fb173f66e745ef7" - integrity sha512-Zs0uTDou/QnYnnXl8BWxX4Tc4bGeUAPzgAyZCdtXT2BIcyb5zjymXByyN7zbqPgiU0Ymbg3DgsK6v726shiq3w== +"@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== dependencies: "@actions/core" "^1.10.1" "@actions/github" "^6.0.0" @@ -1008,11 +1008,11 @@ zod "^3.22.4" "@codecov/vite-plugin@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@codecov/vite-plugin/-/vite-plugin-1.3.0.tgz#502e19427fca4416685f475b891ee10cff1ec274" - integrity sha512-OiseFReYsl5bHYfFd7AZ46ptII+bSQLTRD1qv7CFOmQQLzfrxWtkfqZ+5wQs+9emaWyzERpFI4odDFDvMMS6EA== + 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== dependencies: - "@codecov/bundler-plugin-core" "^1.3.0" + "@codecov/bundler-plugin-core" "^1.4.0" unplugin "^1.10.1" "@csstools/cascade-layer-name-parser@^2.0.4": @@ -1255,7 +1255,7 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-random-function@^1.0.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== @@ -1282,10 +1282,10 @@ dependencies: postcss-selector-parser "^7.0.0" -"@csstools/postcss-sign-functions@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.0.0.tgz#37ea7c85874cef5710a1a8878bf0e2b26d4270fd" - integrity sha512-cUpr5W8eookBi5TiLSvx1HL6DFoTTgcj2pmiVNd63y2JHhvtpnJs3sfsFMmLhB42yTRS02tFPsNz3Q5zeN8ZVA== +"@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== dependencies: "@csstools/css-calc" "^2.1.0" "@csstools/css-parser-algorithms" "^3.0.4" @@ -1628,7 +1628,7 @@ dependencies: "@floating-ui/utils" "^0.2.8" -"@floating-ui/dom@1.6.11", "@floating-ui/dom@^1.0.0": +"@floating-ui/dom@1.6.11": version "1.6.11" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.11.tgz#8631857838d34ee5712339eb7cbdfb8ad34da723" integrity sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ== @@ -1636,6 +1636,14 @@ "@floating-ui/core" "^1.6.0" "@floating-ui/utils" "^0.2.8" +"@floating-ui/dom@^1.0.0": + version "1.6.12" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.12.tgz#6333dcb5a8ead3b2bf82f33d6bc410e95f54e556" + integrity sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.8" + "@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" @@ -1644,9 +1652,9 @@ "@floating-ui/dom" "^1.0.0" "@floating-ui/react@^0.26.24": - version "0.26.25" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.25.tgz#cf4c8a2b89fab1a71712d15e6551df3bfbd2ea1d" - integrity sha512-hZOmgN0NTOzOuZxI1oIrDu3Gcl8WViIkvPMpB4xdd4QD6xAMtwgwr3VPoiyH/bLtRcS1cDnhxLSD1NsMJmwh/A== + version "0.26.28" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7" + integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw== dependencies: "@floating-ui/react-dom" "^2.1.2" "@floating-ui/utils" "^0.2.8" @@ -1667,13 +1675,13 @@ 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.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.3.tgz#dc5a032e1971c709b32b9ab511fa35504a7d3bc9" - integrity sha512-aElGmleuReGnk2wtYOzYFmNWYoiWWmf1pPPCYg0oiIQSJj0mjc4eUfzUXaSOJ4S8WzI/cLqnCTWjqz904FT2OQ== +"@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== dependencies: "@formatjs/fast-memoize" "2.2.3" - "@formatjs/intl-localematcher" "0.5.7" + "@formatjs/intl-localematcher" "0.5.8" tslib "2" "@formatjs/fast-memoize@2.2.3": @@ -1684,28 +1692,28 @@ tslib "2" "@formatjs/intl-durationformat@^0.6.1": - version "0.6.3" - resolved "https://registry.yarnpkg.com/@formatjs/intl-durationformat/-/intl-durationformat-0.6.3.tgz#9c9837fbd7d877ea3039d6c4c7eee59dc1e23e19" - integrity sha512-sI+ssVZpL9ymdF7j4/qzwSwPHdCYVb2jQqQV71tCVEjDRgTz5jj8JqZTymYcV563v1rHKI2nNwNlp2L8D+zJQw== + version "0.6.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl-durationformat/-/intl-durationformat-0.6.4.tgz#ac6b5ab006cf2b57500cce05dd1d201352500471" + integrity sha512-kpYLechF9ZvECzzMsvikBl48GkbCEAbZJN4kG/4x0FTVZkBuOWrBlj6DghCn7YsW3Bgsr0n9E0RYO373Kg3m+Q== dependencies: - "@formatjs/ecma402-abstract" "2.2.3" - "@formatjs/intl-localematcher" "0.5.7" + "@formatjs/ecma402-abstract" "2.2.4" + "@formatjs/intl-localematcher" "0.5.8" tslib "2" -"@formatjs/intl-localematcher@0.5.7": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.7.tgz#f889d076881b785d11ff993b966f527d199436d0" - integrity sha512-GGFtfHGQVFe/niOZp24Kal5b2i36eE2bNL0xi9Sg/yd0TR8aLjcteApZdHmismP5QQax1cMnZM9yWySUUjJteA== +"@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== dependencies: tslib "2" "@formatjs/intl-segmenter@^11.7.3": - version "11.7.3" - resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.3.tgz#aeb49c33c81fec68419922c64c72188b659eaa5a" - integrity sha512-IvEDQRe0t0ouqaqZK2KobGt/+BhwDHdtbS8GWhdl+fjmWbhXMz2mHihu5fAYkYChum5eNfGhEF5P+bLCeYq67w== + version "11.7.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.4.tgz#f99d87ee3f98515069285438a4913681fc243252" + integrity sha512-pyHgFO86/CReKl20oK9jgaTMzSaG/nIMteMW8YuwUcS22EoMI1qbGTZ65oQ38KMT05SiHiMee2CP3WZvCi8YSQ== dependencies: - "@formatjs/ecma402-abstract" "2.2.3" - "@formatjs/intl-localematcher" "0.5.7" + "@formatjs/ecma402-abstract" "2.2.4" + "@formatjs/intl-localematcher" "0.5.8" tslib "2" "@gulpjs/to-absolute-glob@^4.0.0": @@ -1793,9 +1801,9 @@ rxjs "7.8.1" "@livekit/components-react@^2.0.0": - version "2.6.7" - resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.6.7.tgz#12faa30eed232e5cbec6bcb33b7ce449566241af" - integrity sha512-z8dgrBrRXIe7oagwFyjehdwL/4zpySJyPdAjeMDXZVbTXYNAugb3a88Ws9yQz4PZFECLkIPXJCN3C3YR+bgh5Q== + version "2.6.8" + resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.6.8.tgz#faa60410aef0f5d426afcc6f9b577686983c6b7b" + integrity sha512-G6P+mrOyBiAnHjbmBTG28CxA6AT7wXT6/5dqu7M7uZAlvOCDKhPjhOs65awDQvaFlTxd/JlND75fa9d+oSbvIA== dependencies: "@livekit/components-core" "0.11.10" clsx "2.1.1" @@ -2646,61 +2654,61 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sentry-internal/browser-utils@8.37.1": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.37.1.tgz#374028d8e37047aeda14b226707e6601de65996e" - integrity sha512-OSR/V5GCsSCG7iapWtXCT/y22uo3HlawdEgfM1NIKk1mkP15UyGQtGEzZDdih2H+SNuX1mp9jQLTjr5FFp1A5w== +"@sentry-internal/browser-utils@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.38.0.tgz#d7f6d398778906efb0c017e63d3c59d3203dfa7d" + integrity sha512-5QMVcssrAcmjKT0NdFYcX0b0wwZovGAZ9L2GajErXtHkBenjI2sgR2+5J7n+QZGuk2SC1qhGmT1O9i3p3UEwew== dependencies: - "@sentry/core" "8.37.1" - "@sentry/types" "8.37.1" - "@sentry/utils" "8.37.1" + "@sentry/core" "8.38.0" + "@sentry/types" "8.38.0" + "@sentry/utils" "8.38.0" -"@sentry-internal/feedback@8.37.1": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.37.1.tgz#e2d5fc934ca3b4925a5f5d0e63549830a1cf147e" - integrity sha512-Se25NXbSapgS2S+JssR5YZ48b3OY4UGmAuBOafgnMW91LXMxRNWRbehZuNUmjjHwuywABMxjgu+Yp5uJDATX+g== +"@sentry-internal/feedback@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.38.0.tgz#726661a01f7ff40b93c8ee05c985fd0436a1c033" + integrity sha512-AW5HCCAlc3T1jcSuNhbFVNO1CHyJ5g5tsGKEP4VKgu+D1Gg2kZ5S2eFatLBUP/BD5JYb1A7p6XPuzYp1XfMq0A== dependencies: - "@sentry/core" "8.37.1" - "@sentry/types" "8.37.1" - "@sentry/utils" "8.37.1" + "@sentry/core" "8.38.0" + "@sentry/types" "8.38.0" + "@sentry/utils" "8.38.0" -"@sentry-internal/replay-canvas@8.37.1": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.37.1.tgz#e8a5e350e486b16938b3dd99886be23b7b6eff18" - integrity sha512-1JLAaPtn1VL5vblB0BMELFV0D+KUm/iMGsrl4/JpRm0Ws5ESzQl33DhXVv1IX/ZAbx9i14EjR7MG9+Hj70tieQ== +"@sentry-internal/replay-canvas@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.38.0.tgz#26e9bc937dab73e1a26d57dc1015b7ff1f2d76c5" + integrity sha512-OxmlWzK9J8mRM+KxdSnQ5xuxq+p7TiBzTz70FT3HltxmeugvDkyp6803UcFqHOPHR35OYeVLOalym+FmvNn9kw== dependencies: - "@sentry-internal/replay" "8.37.1" - "@sentry/core" "8.37.1" - "@sentry/types" "8.37.1" - "@sentry/utils" "8.37.1" + "@sentry-internal/replay" "8.38.0" + "@sentry/core" "8.38.0" + "@sentry/types" "8.38.0" + "@sentry/utils" "8.38.0" -"@sentry-internal/replay@8.37.1": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.37.1.tgz#6dc2e3955879f6e7ab830db1ddee54e0a9b401f3" - integrity sha512-E/Plhisk/pXJjOdOU12sg8m/APTXTA21iEniidP6jW3/+O0tD/H/UovEqa4odNTqxPMa798xHQSQNt5loYiaLA== +"@sentry-internal/replay@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.38.0.tgz#9a9b945a3c066f5610a363774e3c99420c3f4fce" + integrity sha512-mQPShKnIab7oKwkwrRxP/D8fZYHSkDY+cvqORzgi+wAwgnunytJQjz9g6Ww2lJu98rHEkr5SH4V4rs6PZYZmnQ== dependencies: - "@sentry-internal/browser-utils" "8.37.1" - "@sentry/core" "8.37.1" - "@sentry/types" "8.37.1" - "@sentry/utils" "8.37.1" + "@sentry-internal/browser-utils" "8.38.0" + "@sentry/core" "8.38.0" + "@sentry/types" "8.38.0" + "@sentry/utils" "8.38.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.37.1": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.37.1.tgz#2e6e4accc395ad9e6313e07b09415370c71e5874" - integrity sha512-5ym+iGiIpjIKKpMWi9S3/tXh9xneS+jqxwRTJqed3cb8i4ydfMAAP8sM3U8xMCWWABpWyIUW+fpewC0tkhE1aQ== +"@sentry/browser@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.38.0.tgz#c562accdc2bbe0b0074d98bfe7ff460e39ce3109" + integrity sha512-AZR+b0EteNZEGv6JSdBD22S9VhQ7nrljKsSnzxobBULf3BpwmhmCzTbDrqWszKDAIDYmL+yQJIR2glxbknneWQ== dependencies: - "@sentry-internal/browser-utils" "8.37.1" - "@sentry-internal/feedback" "8.37.1" - "@sentry-internal/replay" "8.37.1" - "@sentry-internal/replay-canvas" "8.37.1" - "@sentry/core" "8.37.1" - "@sentry/types" "8.37.1" - "@sentry/utils" "8.37.1" + "@sentry-internal/browser-utils" "8.38.0" + "@sentry-internal/feedback" "8.38.0" + "@sentry-internal/replay" "8.38.0" + "@sentry-internal/replay-canvas" "8.38.0" + "@sentry/core" "8.38.0" + "@sentry/types" "8.38.0" + "@sentry/utils" "8.38.0" "@sentry/bundler-plugin-core@2.22.6": version "2.22.6" @@ -2770,36 +2778,36 @@ "@sentry/cli-win32-i686" "2.38.1" "@sentry/cli-win32-x64" "2.38.1" -"@sentry/core@8.37.1": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.37.1.tgz#4bafb25c762ec8680874056f6160df276c1cc7c6" - integrity sha512-82csXby589iDupM3VgCHJeWZagUyEEaDnbFcoZ/Z91QX2Sjq8FcF5OsforoXjw09i0XTFqlkFAnQVpDBmMXcpQ== +"@sentry/core@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.38.0.tgz#5d1b74770c79e489e786018a3e514cddeb777bcb" + integrity sha512-sGD+5TEHU9G7X7zpyaoJxpOtwjTjvOd1f/MKBrWW2vf9UbYK+GUJrOzLhMoSWp/pHSYgvObkJkDb/HwieQjvhQ== dependencies: - "@sentry/types" "8.37.1" - "@sentry/utils" "8.37.1" + "@sentry/types" "8.38.0" + "@sentry/utils" "8.38.0" "@sentry/react@^8.0.0": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.37.1.tgz#25ba2703b79436c9154e6f287959a8a3c040e8cb" - integrity sha512-HanDqBFTgIUhUsYztAHhSti+sEhQ8YopAymXgnpqkJ7j1PLHXZgQAre6M4Uvixu28WS5MDHC1onnAIBDgYRDYw== + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.38.0.tgz#513cbd9ba35bb0258d10b74d272800cbc5f05631" + integrity sha512-5396tewO00wbJFHUkmU+ikmp4A+wuBpStNc7UDyAm642jfbPajj51+GWld/ZYNFiQaZ/8I9tvvpHqVLnUh21gg== dependencies: - "@sentry/browser" "8.37.1" - "@sentry/core" "8.37.1" - "@sentry/types" "8.37.1" - "@sentry/utils" "8.37.1" + "@sentry/browser" "8.38.0" + "@sentry/core" "8.38.0" + "@sentry/types" "8.38.0" + "@sentry/utils" "8.38.0" hoist-non-react-statics "^3.3.2" -"@sentry/types@8.37.1": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.37.1.tgz#e92a7d346cfa29116568f4ffb58f65caedee0149" - integrity sha512-ryMOTROLSLINKFEbHWvi7GigNrsQhsaScw2NddybJGztJQ5UhxIGESnxGxWCufBmWFDwd7+5u0jDPCVUJybp7w== +"@sentry/types@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.38.0.tgz#9c48734a8b4055bfd553a0141efec78e9680ed09" + integrity sha512-fP5H9ZX01W4Z/EYctk3mkSHi7d06cLcX2/UWqwdWbyPWI+pL2QpUPICeO/C+8SnmYx//wFj3qWDhyPCh1PdFAA== -"@sentry/utils@8.37.1": - version "8.37.1" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.37.1.tgz#6e020cd222d56d79953ea9d4630d91b3e323ceda" - integrity sha512-Qtn2IfpII12K17txG/ZtTci35XYjYi4CxbQ3j7nXY7toGv/+MqPXwV5q2i9g94XaSXlE5Wy9/hoCZoZpZs/djA== +"@sentry/utils@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.38.0.tgz#2f91ca7d044f6e17b993c866ca02a981c4c1bc25" + integrity sha512-3X7MgIKIx+2q5Al7QkhaRB4wV6DvzYsaeIwdqKUzGLuRjXmNgJrLoU87TAwQRmZ6Wr3IoEpThZZMNrzYPXxArw== dependencies: - "@sentry/types" "8.37.1" + "@sentry/types" "8.38.0" "@sentry/vite-plugin@^2.0.0": version "2.22.6" @@ -3305,14 +3313,16 @@ "@use-gesture/core" "10.3.1" "@vector-im/compound-design-tokens@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.9.1.tgz#644dc7ca5ca251fd476af2a7c075e9d740c08871" - integrity sha512-zjI+PhoNLNrJrLU8whEGjzCuxdqIz6tM0ARYBMS8AG1vC+NlGak6Y21TWnzHT3VINNhnF+PiQ9lFWsU65GydOg== + version "1.9.2" + resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.9.2.tgz#0b76e5475da3bc36443f7dc87951b937b5013d6f" + integrity sha512-gQmK4dHR2iws3ZskDv8Il6A4/rvQV7TPSmEOXLsahDhBTInWqexXeQnNRSt9Z5DsLPrkxL3/KoCt9lfYu/yiag== + dependencies: + prettier "^3.3.3" "@vector-im/compound-web@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.2.0.tgz#0ec4a598e5755cc4b3e83fbc232a4986a12bf808" - integrity sha512-wOT2kSo936FSBG1CsZ1vmHLwTTWBq9OBBfq76sM95rUawRSQCCWnjFMLTiacRvxBHucZaSNsfhpJH3oZcrOexw== + version "7.3.0" + resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.3.0.tgz#9594113ac50bff4794715104a30a60c52d15517d" + integrity sha512-gDppQUtpk5LvNHUg+Zlv9qzo1iBAag0s3g8Ec0qS5q4zGBKG6ruXXrNUKg1aK8rpbo2hYQsGaHM6dD8NqLoq3Q== dependencies: "@floating-ui/react" "^0.26.24" "@radix-ui/react-context-menu" "^2.2.1" @@ -3342,9 +3352,9 @@ react-refresh "^0.14.2" "@vitest/coverage-v8@^2.0.5": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz#c0df11cda12b3a04570e8065754917d35baa0c55" - integrity sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ== + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz#74ef3bf6737f9897a54af22f820d90e85883ff83" + integrity sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw== dependencies: "@ampproject/remapping" "^2.3.0" "@bcoe/v8-coverage" "^0.2.3" @@ -3355,66 +3365,66 @@ istanbul-reports "^3.1.7" magic-string "^0.30.12" magicast "^0.3.5" - std-env "^3.7.0" + std-env "^3.8.0" test-exclude "^7.0.1" tinyrainbow "^1.2.0" -"@vitest/expect@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.4.tgz#48f4f53a01092a3bdc118cff245f79ef388bdd8e" - integrity sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA== +"@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== dependencies: - "@vitest/spy" "2.1.4" - "@vitest/utils" "2.1.4" + "@vitest/spy" "2.1.5" + "@vitest/utils" "2.1.5" chai "^5.1.2" tinyrainbow "^1.2.0" -"@vitest/mocker@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.4.tgz#0dc07edb9114f7f080a0181fbcdb16cd4a2d855d" - integrity sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ== +"@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== dependencies: - "@vitest/spy" "2.1.4" + "@vitest/spy" "2.1.5" estree-walker "^3.0.3" magic-string "^0.30.12" -"@vitest/pretty-format@2.1.4", "@vitest/pretty-format@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.4.tgz#fc31993bdc1ef5a6c1a4aa6844e7ba55658a4f9f" - integrity sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww== +"@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== dependencies: tinyrainbow "^1.2.0" -"@vitest/runner@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.4.tgz#f9346500bdd0be1c926daaac5d683bae87ceda2c" - integrity sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA== +"@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== dependencies: - "@vitest/utils" "2.1.4" + "@vitest/utils" "2.1.5" pathe "^1.1.2" -"@vitest/snapshot@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.4.tgz#ef8c3f605fbc23a32773256d37d3fdfd9b23d353" - integrity sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q== +"@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== dependencies: - "@vitest/pretty-format" "2.1.4" + "@vitest/pretty-format" "2.1.5" magic-string "^0.30.12" pathe "^1.1.2" -"@vitest/spy@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.4.tgz#4e90f9783437c5841a27c80f8fd84d7289a6100a" - integrity sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg== +"@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== dependencies: tinyspy "^3.0.2" -"@vitest/utils@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.4.tgz#6d67ac966647a21ce8bc497472ce230de3b64537" - integrity sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg== +"@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== dependencies: - "@vitest/pretty-format" "2.1.4" + "@vitest/pretty-format" "2.1.5" loupe "^3.1.2" tinyrainbow "^1.2.0" @@ -4138,7 +4148,16 @@ cosmiconfig@^8.1.3: parse-json "^5.2.0" path-type "^4.0.0" -cross-spawn@^7.0.0, cross-spawn@^7.0.2: +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== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -4436,9 +4455,9 @@ easy-table@1.2.0: wcwidth "^1.0.1" electron-to-chromium@^1.5.41: - version "1.5.55" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz#73684752aa2e1aa49cafb355a41386c6637e76a9" - integrity sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg== + version "1.5.62" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.62.tgz#8289468414b0b0b3e9180ef619a763555debe612" + integrity sha512-t8c+zLmJHa9dJy96yBZRXGQYoiCEnHYgFwn1asvSPZSUdVxnB62A4RASd7k41ytG3ErFBA0TpHlKg9D9SQBmLg== emoji-regex@^8.0.0: version "8.0.0" @@ -4572,6 +4591,11 @@ es-iterator-helpers@^1.1.0: iterator.prototype "^1.1.3" safe-array-concat "^1.1.2" +es-module-lexer@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -5509,10 +5533,10 @@ ignore@^5.1.8, ignore@^5.2.0, ignore@^5.3.1: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -immutable@^4.0.0: - version "4.3.7" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" - integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== +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== import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" @@ -5986,9 +6010,9 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== knip@^5.27.2: - version "5.36.6" - resolved "https://registry.yarnpkg.com/knip/-/knip-5.36.6.tgz#24a0af598b99728c4c80bb823d70c276b732899b" - integrity sha512-9Id0az4KDqdiSWEkzGh9qPXs84gvp+4fPeYF8k3ogiJK98EjAgPGAVWcdtrcIj1fgs4OSiGe/e7UOdy3wXhbTA== + version "5.37.1" + resolved "https://registry.yarnpkg.com/knip/-/knip-5.37.1.tgz#3c3e91c425dfb35be68b4d12cc0b9ee3cde794e8" + integrity sha512-69gjKj5lLsLXcIPXlHyFfX5AOHgRdh/iXH8gUqvmsHtjqoWhOATeXZDjvvemmgw7KxbWbUzxBNbpjhtJWzgqGA== dependencies: "@nodelib/fs.walk" "1.2.8" "@snyk/github-codeowners" "1.1.0" @@ -6043,9 +6067,9 @@ lines-and-columns@^1.1.6: integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== livekit-client@^2.5.7: - version "2.6.0" - resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-2.6.0.tgz#7506838fb59937150208b8e593701cde19f3fb75" - integrity sha512-hpxNBtyWIFCefoHjHoSjqPCw3m7AfSJVcVZw6rMsqds4u+dSpWLfYkglWP8JuPGUIssyOsZm/+bV3gBWfuOGGQ== + version "2.6.2" + resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-2.6.2.tgz#7821cac8d293b7685a4272b8aa269685f0ae75a8" + integrity sha512-SqXNHLgk2ZZOZyeHLXFAVAl+FVdSI+NK39LvIYstqS5X6IE5aCPlK4FqXY4l3aHpSft/BC/TR1CFGOq20ONkMA== dependencies: "@livekit/mutex" "1.0.0" "@livekit/protocol" "1.24.0" @@ -6825,9 +6849,9 @@ postcss-place@^10.0.0: postcss-value-parser "^4.2.0" postcss-preset-env@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.1.0.tgz#b9c85d25b26c7581a8b11792b1883469c4b69cbf" - integrity sha512-OfzbinZWpFcmuLB3mabsGa0NArzx5DVVtZ9G1k326iLvU7Jj9q/G3ihBu/Msi0mt96CjrM23HpbuEewDvT71KQ== + version "10.1.1" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.1.1.tgz#6ee631272353fb1c4a9711943e9b80a178ffce44" + integrity sha512-wqqsnBFD6VIwcHHRbhjTOcOi4qRVlB26RwSr0ordPj7OubRRxdWebv/aLjKLRR8zkZrbxZyuus03nOIgC5elMQ== dependencies: "@csstools/postcss-cascade-layers" "^5.0.1" "@csstools/postcss-color-function" "^4.0.6" @@ -6853,10 +6877,10 @@ postcss-preset-env@^10.0.0: "@csstools/postcss-normalize-display-values" "^4.0.0" "@csstools/postcss-oklab-function" "^4.0.6" "@csstools/postcss-progressive-custom-properties" "^4.0.0" - "@csstools/postcss-random-function" "^1.0.0" + "@csstools/postcss-random-function" "^1.0.1" "@csstools/postcss-relative-color-syntax" "^3.0.6" "@csstools/postcss-scope-pseudo-class" "^4.0.1" - "@csstools/postcss-sign-functions" "^1.0.0" + "@csstools/postcss-sign-functions" "^1.1.0" "@csstools/postcss-stepped-value-functions" "^4.0.5" "@csstools/postcss-text-decoration-shorthand" "^4.0.1" "@csstools/postcss-trigonometric-functions" "^4.0.5" @@ -6926,9 +6950,9 @@ postcss-value-parser@^4.2.0: integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.4.41, postcss@^8.4.43: - version "8.4.48" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.48.tgz#765f3f8abaa2a2b065cdddbc57ad4cb5a76e515f" - integrity sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA== + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== dependencies: nanoid "^3.3.7" picocolors "^1.1.1" @@ -6953,7 +6977,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^3.0.0: +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== @@ -6968,9 +6992,9 @@ pretty-format@^27.0.2: react-is "^17.0.1" pretty-ms@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-9.1.0.tgz#0ad44de6086454f48a168e5abb3c26f8db1b3253" - integrity sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw== + version "9.2.0" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-9.2.0.tgz#e14c0aad6493b69ed63114442a84133d7e560ef0" + integrity sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg== dependencies: parse-ms "^4.0.0" @@ -7513,12 +7537,12 @@ safe-regex-test@^1.0.3: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass@^1.42.1: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.80.6.tgz#5d0aa55763984effe41e40019c9571ab73e6851f" - integrity sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg== + version "1.81.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.81.0.tgz#a9010c0599867909dfdbad057e4a6fbdd5eec941" + integrity sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA== dependencies: chokidar "^4.0.0" - immutable "^4.0.0" + immutable "^5.0.2" source-map-js ">=0.6.2 <2.0.0" optionalDependencies: "@parcel/watcher" "^2.4.1" @@ -7701,10 +7725,10 @@ stackback@0.0.2: resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== -std-env@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" - integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +std-env@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" + integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== stream-composer@^1.0.2: version "1.0.2" @@ -7975,9 +7999,9 @@ tinyexec@^0.3.1: integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ== tinypool@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe" - integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA== + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2" + integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA== tinyrainbow@^1.2.0: version "1.2.0" @@ -8064,7 +8088,7 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2, tslib@^2.0.3: +tslib@2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -8079,11 +8103,6 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.1.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" - integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== - tsutils-etc@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/tsutils-etc/-/tsutils-etc-1.4.2.tgz#6d6a9f33aa61867d832e4a455b2cebb6b104ebfa" @@ -8411,13 +8430,14 @@ vinyl@^3.0.0, vinyl@~3.0.0: replace-ext "^2.0.0" teex "^1.0.1" -vite-node@2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.4.tgz#97ffb6de913fd8d42253afe441f9512e9dbdfd5c" - integrity sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg== +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== dependencies: cac "^6.7.14" debug "^4.3.7" + es-module-lexer "^1.5.4" pathe "^1.1.2" vite "^5.0.0" @@ -8468,29 +8488,29 @@ vitest-axe@^1.0.0-pre.3: lodash-es "^4.17.21" vitest@^2.0.0: - version "2.1.4" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.4.tgz#ba8f4589fb639cf5a9e6af54781667312b3e8230" - integrity sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ== + version "2.1.5" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.5.tgz#a93b7b84a84650130727baae441354e6df118148" + integrity sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A== dependencies: - "@vitest/expect" "2.1.4" - "@vitest/mocker" "2.1.4" - "@vitest/pretty-format" "^2.1.4" - "@vitest/runner" "2.1.4" - "@vitest/snapshot" "2.1.4" - "@vitest/spy" "2.1.4" - "@vitest/utils" "2.1.4" + "@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" chai "^5.1.2" debug "^4.3.7" expect-type "^1.1.0" magic-string "^0.30.12" pathe "^1.1.2" - std-env "^3.7.0" + std-env "^3.8.0" tinybench "^2.9.0" tinyexec "^0.3.1" tinypool "^1.0.1" tinyrainbow "^1.2.0" vite "^5.0.0" - vite-node "2.1.4" + vite-node "2.1.5" why-is-node-running "^2.3.0" void-elements@3.1.0: