mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-24 00:38:31 +08:00
Merge remote-tracking branch 'origin/livekit' into hs/add-buttons-for-reactions
This commit is contained in:
commit
547f74718b
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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 ({{keyboardShortcut}})",
|
||||
"no": "No",
|
||||
"open_search": "Open search",
|
||||
"pick_reaction": "Pick reaction",
|
||||
"raise_hand": "Raise hand ({{keyboardShortcut}})",
|
||||
"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</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>",
|
||||
"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",
|
||||
|
@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.png" />
|
||||
<link rel="preload" href="/config.json" as="fetch" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
|
||||
|
@ -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);
|
||||
|
@ -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%);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
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;
|
||||
|
@ -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 { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
@ -116,7 +115,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);
|
||||
@ -124,9 +123,7 @@ test("Can search for and send emoji", async () => {
|
||||
<TestComponent rtcSession={rtcSession} />,
|
||||
);
|
||||
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("🦗"));
|
||||
|
||||
@ -146,39 +143,7 @@ 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(
|
||||
<TestComponent rtcSession={rtcSession} />,
|
||||
);
|
||||
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);
|
||||
@ -186,23 +151,7 @@ test("Can close search", async () => {
|
||||
<TestComponent rtcSession={rtcSession} />,
|
||||
);
|
||||
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(
|
||||
<TestComponent rtcSession={rtcSession} />,
|
||||
);
|
||||
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();
|
||||
});
|
||||
|
@ -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,
|
||||
@ -76,43 +68,11 @@ export function ReactionPopupMenu({
|
||||
canReact: boolean;
|
||||
}): ReactNode {
|
||||
const { t } = useTranslation();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const onSearch = useCallback<ChangeEventHandler<HTMLInputElement>>((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, ReactionsRowSize),
|
||||
[searchText, isSearching],
|
||||
);
|
||||
|
||||
const onSearchKeyDown = useCallback<KeyboardEventHandler<never>>(
|
||||
(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", { keyboardShortcut: "H" })
|
||||
@ -143,36 +103,15 @@ export function ReactionPopupMenu({
|
||||
</Tooltip>
|
||||
</section>
|
||||
<div className={styles.verticalSeperator} />
|
||||
<section>
|
||||
{isSearching ? (
|
||||
<>
|
||||
<Form.Root className={styles.searchForm}>
|
||||
<Search
|
||||
required
|
||||
value={searchText}
|
||||
name="reactionSearch"
|
||||
placeholder={t("reaction_search")}
|
||||
onChange={onSearch}
|
||||
onKeyDown={onSearchKeyDown}
|
||||
// This is a reasonable use of autofocus, we are focusing when
|
||||
// the search button is clicked (which matches the Element Web reaction picker)
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
/>
|
||||
<CpdButton
|
||||
Icon={CloseIcon}
|
||||
aria-label={t("action.close_search")}
|
||||
size="sm"
|
||||
kind="destructive"
|
||||
onClick={() => setIsSearching(false)}
|
||||
/>
|
||||
</Form.Root>
|
||||
</>
|
||||
) : null}
|
||||
<menu className={styles.reactionsMenu}>
|
||||
<section className={styles.reactionsMenuSection}>
|
||||
<menu
|
||||
className={classNames(
|
||||
isFullyExpanded && styles.reactionsMenuExpanded,
|
||||
styles.reactionsMenu,
|
||||
)}
|
||||
>
|
||||
{filteredReactionSet.map((reaction, index) => (
|
||||
<li className={styles.reactionPopupMenuItem} key={reaction.name}>
|
||||
{/* Show the keyboard key assigned to the reaction */}
|
||||
<li key={reaction.name}>
|
||||
<Tooltip
|
||||
label={
|
||||
index < ReactionsRowSize
|
||||
@ -198,21 +137,23 @@ export function ReactionPopupMenu({
|
||||
))}
|
||||
</menu>
|
||||
</section>
|
||||
{!isSearching ? (
|
||||
<section style={{ marginLeft: "var(--cpd-separator-spacing)" }}>
|
||||
<li key="search" className={styles.reactionPopupMenuItem}>
|
||||
<Tooltip label={t("common.search")}>
|
||||
<Tooltip
|
||||
label={
|
||||
isFullyExpanded ? t("action.show_less") : t("action.show_more")
|
||||
}
|
||||
>
|
||||
<CpdButton
|
||||
iconOnly
|
||||
aria-label={t("action.open_search")}
|
||||
Icon={SearchIcon}
|
||||
aria-label={
|
||||
isFullyExpanded ? t("action.show_less") : t("action.show_more")
|
||||
}
|
||||
Icon={isFullyExpanded ? ChevronUpIcon : ChevronDownIcon}
|
||||
kind="tertiary"
|
||||
onClick={() => setIsSearching(true)}
|
||||
onClick={() => setExpanded(!isFullyExpanded)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -289,12 +230,13 @@ export function ReactionToggleButton({
|
||||
title={t("action.pick_reaction")}
|
||||
hideHeader
|
||||
classNameModal={styles.reactionPopupMenuModal}
|
||||
className={styles.reactionPopupMenuRoot}
|
||||
onDismiss={() => setShowReactionsMenu(false)}
|
||||
>
|
||||
<ReactionPopupMenu
|
||||
errorText={errorText}
|
||||
isHandRaised={isHandRaised}
|
||||
canReact={canReact}
|
||||
canReact={!busy && canReact}
|
||||
sendReaction={(reaction) => void sendRelation(reaction)}
|
||||
toggleRaisedHand={wrappedToggleRaisedHand}
|
||||
/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Can close search 1`] = `
|
||||
exports[`Can close reaction dialog 1`] = `
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-aria-hidden="true"
|
||||
@ -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`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Can close search with the escape key 1`] = `
|
||||
<div>
|
||||
exports[`Can fully expand emoji picker 1`] = `
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-aria-hidden="true"
|
||||
>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="true"
|
||||
aria-label="action.raise_hand_or_send_reaction"
|
||||
aria-labelledby=":rhh:"
|
||||
aria-labelledby=":r6c:"
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||
data-kind="secondary"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@ -76,7 +79,7 @@ exports[`Can lower hand 1`] = `
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="action.raise_hand_or_send_reaction"
|
||||
aria-labelledby=":r3i:"
|
||||
aria-labelledby=":r36:"
|
||||
class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
@ -142,7 +145,7 @@ exports[`Can raise hand 1`] = `
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="action.raise_hand_or_send_reaction"
|
||||
aria-labelledby=":r1p:"
|
||||
aria-labelledby=":r1j:"
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||
data-kind="secondary"
|
||||
data-size="lg"
|
||||
@ -167,75 +170,3 @@ exports[`Can raise hand 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Can search for and send emoji 1`] = `
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-aria-hidden="true"
|
||||
>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="true"
|
||||
aria-label="action.raise_hand_or_send_reaction"
|
||||
aria-labelledby=":r74:"
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.536 15.536a1 1 0 0 0-1.415-1.415 2.987 2.987 0 0 1-2.12.879 2.988 2.988 0 0 1-2.122-.879 1 1 0 1 0-1.414 1.415A4.987 4.987 0 0 0 12 17c1.38 0 2.632-.56 3.536-1.464ZM10 10.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm5.5 1.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
|
||||
/>
|
||||
<path
|
||||
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Can search for and send emoji with the keyboard 1`] = `
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-aria-hidden="true"
|
||||
>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="true"
|
||||
aria-label="action.raise_hand_or_send_reaction"
|
||||
aria-labelledby=":ra3:"
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.536 15.536a1 1 0 0 0-1.415-1.415 2.987 2.987 0 0 1-2.12.879 2.988 2.988 0 0 1-2.122-.879 1 1 0 1 0-1.414 1.415A4.987 4.987 0 0 0 12 17c1.38 0 2.632-.56 3.536-1.464ZM10 10.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm5.5 1.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
|
||||
/>
|
||||
<path
|
||||
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
@ -28,11 +28,11 @@ export class Config {
|
||||
const internalInstance = new Config();
|
||||
Config.internalInstance = internalInstance;
|
||||
|
||||
Config.internalInstance.initPromise = downloadConfig(
|
||||
"../config.json",
|
||||
).then((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<ConfigOptions> {
|
||||
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.
|
||||
|
@ -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 {
|
||||
|
16
src/useIsTouchscreen.ts
Normal file
16
src/useIsTouchscreen.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useMediaQuery } from "./useMediaQuery";
|
||||
|
||||
/**
|
||||
* @returns Whether the device is a touchscreen device.
|
||||
*/
|
||||
// Empirically, Chrome on Android can end up not matching (hover: none), but
|
||||
// still matching (pointer: coarse) :/
|
||||
export const useIsTouchscreen = (): boolean =>
|
||||
useMediaQuery("(hover: none) or (pointer: coarse)");
|
Loading…
Reference in New Issue
Block a user