From 2e172aa3be672f3a4758d385020470c04b3527a6 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 15 Nov 2024 13:25:33 +0100 Subject: [PATCH 1/3] Pre-fetch the config.json to improve startup time --- config/nginx.conf | 2 +- public/index.html | 1 + src/config/Config.ts | 16 ++++++---------- 3 files changed, 8 insertions(+), 11 deletions(-) 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/public/index.html b/public/index.html index df1edfd9..e14d79be 100644 --- a/public/index.html +++ b/public/index.html @@ -3,6 +3,7 @@ + { - internalInstance.config = merge({}, DEFAULT_CONFIG, config); - }); + Config.internalInstance.initPromise = downloadConfig("/config.json").then( + (config) => { + internalInstance.config = merge({}, DEFAULT_CONFIG, config); + }, + ); } return Config.internalInstance.initPromise; } @@ -74,11 +74,7 @@ async function downloadConfig( configJsonFilename: string, ): Promise { const url = new URL(configJsonFilename, window.location.href); - url.searchParams.set("cachebuster", Date.now().toString()); - const res = await fetch(url, { - cache: "no-cache", - method: "GET", - }); + const res = await fetch(url); if (!res.ok || res.status === 404 || res.status === 0) { // Lack of a config isn't an error, we should just use the defaults. From c1208ac7b6a19adc2dd6c645db373c6fd994d562 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 15 Nov 2024 14:09:52 +0000 Subject: [PATCH 2/3] Small improvements to settings tab (#2789) * align profile tab. * Fix descriptions --- locales/de/app.json | 4 ++-- locales/en-GB/app.json | 4 ++-- src/settings/ProfileSettingsTab.module.css | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/locales/de/app.json b/locales/de/app.json index 744fa792..b73d8c3c 100644 --- a/locales/de/app.json +++ b/locales/de/app.json @@ -163,8 +163,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..da713a59 100644 --- a/locales/en-GB/app.json +++ b/locales/en-GB/app.json @@ -163,8 +163,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/src/settings/ProfileSettingsTab.module.css b/src/settings/ProfileSettingsTab.module.css index 756c6fbf..a87ed8bf 100644 --- a/src/settings/ProfileSettingsTab.module.css +++ b/src/settings/ProfileSettingsTab.module.css @@ -8,7 +8,8 @@ Please see LICENSE in the repository root for full details. .content { width: 100%; max-width: 350px; - align-self: center; + margin-left: auto; + margin-right: auto; } .avatarFieldRow { From eed1b98cf9efe1983eca45465a59a189d64d2e60 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 15 Nov 2024 16:02:06 +0000 Subject: [PATCH 3/3] Update reactions to new design (#2751) * Replace search strings * Add expander for reactions view * lint * Remove useless list * Update tests * lint * Only shrink buttons on web version, mobile never shrinks * Revert accidental change. * Remove border from button * Tidy up views. * Finish matching designs. * Fix height * Remove a unneeded class * Remove more cruft * Remove unnessacery chunk * Evenly space reactions * lint * Disable reaction buttons when busy * Try to make menu a bit more responsive * Update test * further screen size tweaks --- locales/en-GB/app.json | 6 +- src/Modal.module.css | 3 +- src/Overlay.module.css | 3 +- src/button/ReactionToggleButton.module.css | 107 +++++++++------ src/button/ReactionToggleButton.test.tsx | 61 +-------- src/button/ReactionToggleButton.tsx | 123 +++++------------- .../ReactionToggleButton.test.tsx.snap | 91 ++----------- 7 files changed, 119 insertions(+), 275 deletions(-) diff --git a/locales/en-GB/app.json b/locales/en-GB/app.json index da713a59..9366ecd0 100644 --- a/locales/en-GB/app.json +++ b/locales/en-GB/app.json @@ -4,19 +4,19 @@ }, "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 +62,6 @@ "preferences": "Preferences", "profile": "Profile", "reaction": "Reaction", - "search": "Search", "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", diff --git a/src/Modal.module.css b/src/Modal.module.css index fae3a6fb..fc4cf366 100644 --- a/src/Modal.module.css +++ b/src/Modal.module.css @@ -6,6 +6,7 @@ Please see LICENSE in the repository root for full details. */ .modal { + --inset-inline: 520px; display: flex; flex-direction: column; } @@ -35,7 +36,7 @@ Please see LICENSE in the repository root for full details. .drawer { background: var(--cpd-color-bg-canvas-default); inset-block-end: 0; - inset-inline: max(0px, calc((100% - 520px) / 2)); + inset-inline: max(0px, calc((100% - var(--inset-inline)) / 2)); max-block-size: 90%; border-start-start-radius: var(--border-radius); border-start-end-radius: var(--border-radius); diff --git a/src/Overlay.module.css b/src/Overlay.module.css index 7f24e7f1..6253bcb0 100644 --- a/src/Overlay.module.css +++ b/src/Overlay.module.css @@ -42,8 +42,9 @@ Please see LICENSE in the repository root for full details. } .overlay.animate { + --overlay-top: 50%; left: 50%; - top: 50%; + top: var(--overlay-top); transform: translate(-50%, -50%); } diff --git a/src/button/ReactionToggleButton.module.css b/src/button/ReactionToggleButton.module.css index 7c97c2de..90c6af02 100644 --- a/src/button/ReactionToggleButton.module.css +++ b/src/button/ReactionToggleButton.module.css @@ -3,78 +3,99 @@ } .reactionPopupMenu { + --reaction-button-padding: 10px; + --reaction-button-fontsize: 20px; + --reaction-button-gap: var(--cpd-separator-spacing); display: flex; + width: fit-content; } -.reactionPopupMenuModal { - width: fit-content !important; - top: 82vh !important; +@media (max-width: 420px) { + .reactionPopupMenu { + --reaction-button-padding: 8px; + --reaction-button-fontsize: 16px; + --reaction-button-gap: 6px; + } } -.reactionPopupMenuModal > 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..14218820 100644 --- a/src/button/ReactionToggleButton.test.tsx +++ b/src/button/ReactionToggleButton.test.tsx @@ -5,8 +5,7 @@ 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"; @@ -122,7 +121,7 @@ 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); @@ -130,9 +129,7 @@ test("Can search for and send emoji", async () => { , ); 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("action.show_more")); expect(container).toMatchSnapshot(); await user.click(getByText("🦗")); @@ -152,38 +149,6 @@ 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 () => { const user = userEvent.setup(); const room = new MockRoom(memberUserIdAlice); @@ -192,23 +157,7 @@ test("Can close search", async () => { , ); 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("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..1c049497 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, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { - ChangeEventHandler, ComponentPropsWithoutRef, FC, - KeyboardEventHandler, ReactNode, useCallback, useEffect, @@ -84,43 +76,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 ( @@ -148,35 +108,15 @@ export function ReactionPopupMenu({
-
- {isSearching ? ( - <> - - - setIsSearching(false)} - /> - - - ) : null} - +
+ {filteredReactionSet.map((reaction) => ( -
  • +
  • - {!isSearching ? ( -
    -
  • - - setIsSearching(true)} - /> - -
  • -
    - ) : null} +
    + + setExpanded(!isFullyExpanded)} + /> + +
    ); @@ -335,12 +277,13 @@ export function ReactionToggleButton({ title={t("action.pick_reaction")} hideHeader classNameModal={styles.reactionPopupMenuModal} + className={styles.reactionPopupMenuRoot} onDismiss={() => setShowReactionsMenu(false)} > void sendRelation(reaction)} toggleRaisedHand={toggleRaisedHand} /> diff --git a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap index bee0bdb1..604a615a 100644 --- a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap +++ b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap @@ -10,7 +10,7 @@ exports[`Can close search 1`] = ` aria-expanded="true" aria-haspopup="true" aria-label="action.raise_hand_or_send_reaction" - aria-labelledby=":rec:" + aria-labelledby=":r9l:" class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" data-kind="primary" data-size="lg" @@ -36,16 +36,19 @@ exports[`Can close search 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`] = ` - -`;