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 / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
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
|
# assets can be cached because they have hashed filenames
|
||||||
|
@ -163,8 +163,8 @@
|
|||||||
"preferences_tab": {
|
"preferences_tab": {
|
||||||
"reactions_play_sound_description": "Einen Soundeffekt abspielen, wenn jemand eine Reaktion sendet",
|
"reactions_play_sound_description": "Einen Soundeffekt abspielen, wenn jemand eine Reaktion sendet",
|
||||||
"reactions_play_sound_label": "Reaktionstöne abspielen",
|
"reactions_play_sound_label": "Reaktionstöne abspielen",
|
||||||
"reactions_show_description": "Reaktionen anzeigen",
|
"reactions_show_description": "Zeige eine Animation, wenn jemand eine Reaktion sendet.",
|
||||||
"reactions_show_label": "Zeige eine Animation, wenn jemand eine Reaktion sendet.",
|
"reactions_show_label": "Reaktionen anzeigen",
|
||||||
"reactions_title": "Reaktionen"
|
"reactions_title": "Reaktionen"
|
||||||
},
|
},
|
||||||
"preferences_tab_body": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden",
|
"preferences_tab_body": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden",
|
||||||
|
@ -4,19 +4,19 @@
|
|||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"close_search": "Close search",
|
|
||||||
"copy_link": "Copy link",
|
"copy_link": "Copy link",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"go": "Go",
|
"go": "Go",
|
||||||
"invite": "Invite",
|
"invite": "Invite",
|
||||||
"lower_hand": "Lower hand ({{keyboardShortcut}})",
|
"lower_hand": "Lower hand ({{keyboardShortcut}})",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"open_search": "Open search",
|
|
||||||
"pick_reaction": "Pick reaction",
|
"pick_reaction": "Pick reaction",
|
||||||
"raise_hand": "Raise hand ({{keyboardShortcut}})",
|
"raise_hand": "Raise hand ({{keyboardShortcut}})",
|
||||||
"raise_hand_or_send_reaction": "Raise hand or send reaction",
|
"raise_hand_or_send_reaction": "Raise hand or send reaction",
|
||||||
"register": "Register",
|
"register": "Register",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
|
"show_less": "Show less",
|
||||||
|
"show_more": "Show more",
|
||||||
"sign_in": "Sign in",
|
"sign_in": "Sign in",
|
||||||
"sign_out": "Sign out",
|
"sign_out": "Sign out",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
@ -62,7 +62,6 @@
|
|||||||
"preferences": "Preferences",
|
"preferences": "Preferences",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"reaction": "Reaction",
|
"reaction": "Reaction",
|
||||||
"search": "Search",
|
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"something_went_wrong": "Something went wrong",
|
"something_went_wrong": "Something went wrong",
|
||||||
"unencrypted": "Not encrypted",
|
"unencrypted": "Not encrypted",
|
||||||
@ -128,7 +127,6 @@
|
|||||||
"rageshake_sending": "Sending…",
|
"rageshake_sending": "Sending…",
|
||||||
"rageshake_sending_logs": "Sending debug logs…",
|
"rageshake_sending_logs": "Sending debug logs…",
|
||||||
"rageshake_sent": "Thanks!",
|
"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_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_dismissed": "Recaptcha dismissed",
|
||||||
"recaptcha_not_loaded": "Recaptcha not loaded",
|
"recaptcha_not_loaded": "Recaptcha not loaded",
|
||||||
@ -163,8 +161,8 @@
|
|||||||
"preferences_tab": {
|
"preferences_tab": {
|
||||||
"reactions_play_sound_description": "Play a sound effect when anyone sends a reaction into a call.",
|
"reactions_play_sound_description": "Play a sound effect when anyone sends a reaction into a call.",
|
||||||
"reactions_play_sound_label": "Play reaction sounds",
|
"reactions_play_sound_label": "Play reaction sounds",
|
||||||
"reactions_show_description": "Show reactions",
|
"reactions_show_description": "Show an animation when anyone sends a reaction.",
|
||||||
"reactions_show_label": "Show an animation when anyone sends a reaction.",
|
"reactions_show_label": "Show reactions",
|
||||||
"reactions_title": "Reactions"
|
"reactions_title": "Reactions"
|
||||||
},
|
},
|
||||||
"preferences_tab_body": "Here you can configure extra options for an improved experience",
|
"preferences_tab_body": "Here you can configure extra options for an improved experience",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.png" />
|
<link rel="icon" type="image/svg+xml" href="favicon.png" />
|
||||||
|
<link rel="preload" href="/config.json" as="fetch" />
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
|
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 {
|
.modal {
|
||||||
|
--inset-inline: 520px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@ -35,7 +36,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
.drawer {
|
.drawer {
|
||||||
background: var(--cpd-color-bg-canvas-default);
|
background: var(--cpd-color-bg-canvas-default);
|
||||||
inset-block-end: 0;
|
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%;
|
max-block-size: 90%;
|
||||||
border-start-start-radius: var(--border-radius);
|
border-start-start-radius: var(--border-radius);
|
||||||
border-start-end-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.animate {
|
||||||
|
--overlay-top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: var(--overlay-top);
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,78 +3,99 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.reactionPopupMenu {
|
.reactionPopupMenu {
|
||||||
|
--reaction-button-padding: 10px;
|
||||||
|
--reaction-button-fontsize: 20px;
|
||||||
|
--reaction-button-gap: var(--cpd-separator-spacing);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionPopupMenuModal {
|
@media (max-width: 420px) {
|
||||||
width: fit-content !important;
|
.reactionPopupMenu {
|
||||||
top: 82vh !important;
|
--reaction-button-padding: 8px;
|
||||||
|
--reaction-button-fontsize: 16px;
|
||||||
|
--reaction-button-gap: 6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionPopupMenuModal > div > div {
|
div.reactionPopupMenuRoot.reactionPopupMenuModal {
|
||||||
padding-inline: var(--cpd-space-6x) !important;
|
--overlay-top: 82vh;
|
||||||
padding-block: var(--cpd-space-6x) var(--cpd-space-8x) !important;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionPopupMenu menu {
|
div.reactionPopupMenuRoot {
|
||||||
margin: 0;
|
/* Center the drawer */
|
||||||
padding: 0;
|
--inset-inline: 30em;
|
||||||
display: flex;
|
}
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: var(--cpd-separator-spacing);
|
.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 {
|
.reactionPopupMenu section {
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
margin-top: auto;
|
flex: 1;
|
||||||
margin-bottom: auto;
|
max-width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionPopupMenuItem {
|
.reactionPopupMenu section.reactionsMenuSection {
|
||||||
list-style: none;
|
margin: auto 0;
|
||||||
|
flex: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionsMenu {
|
.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 {
|
.reactionButton {
|
||||||
padding: 1em;
|
padding: var(--reaction-button-padding);
|
||||||
font-size: 1.6em;
|
|
||||||
width: 1.4em;
|
|
||||||
height: 1.4em;
|
|
||||||
border-radius: var(--cpd-radius-pill-effect);
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
}
|
font-size: var(--reaction-button-fontsize);
|
||||||
|
min-block-size: unset;
|
||||||
@media (max-width: 800px) {
|
border: none;
|
||||||
.reactionButton {
|
aspect-ratio: 1 / 1;
|
||||||
padding: 1em;
|
height: 100%;
|
||||||
font-size: 1em;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
min-block-size: unset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.verticalSeperator {
|
.verticalSeperator {
|
||||||
background-color: var(--cpd-color-gray-800);
|
background-color: var(--cpd-color-gray-800);
|
||||||
width: 1px;
|
width: 2px;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin-left: var(--cpd-separator-spacing);
|
margin-left: var(--cpd-separator-spacing);
|
||||||
margin-right: 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 {
|
.alert {
|
||||||
margin-bottom: var(--cpd-space-3x);
|
margin-bottom: var(--cpd-space-3x);
|
||||||
animation: grow-in 200ms;
|
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.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fireEvent, render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { act } from "react";
|
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
import { userEvent } from "@testing-library/user-event";
|
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 user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const room = new MockRoom(memberUserIdAlice);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
@ -124,9 +123,7 @@ test("Can search for and send emoji", async () => {
|
|||||||
<TestComponent rtcSession={rtcSession} />,
|
<TestComponent rtcSession={rtcSession} />,
|
||||||
);
|
);
|
||||||
await user.click(getByLabelText("action.raise_hand_or_send_reaction"));
|
await user.click(getByLabelText("action.raise_hand_or_send_reaction"));
|
||||||
await user.click(getByLabelText("action.open_search"));
|
await user.click(getByLabelText("action.show_more"));
|
||||||
// Search should autofocus.
|
|
||||||
await user.keyboard("crickets");
|
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
await user.click(getByText("🦗"));
|
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 () => {
|
test("Can close reaction dialog", 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 () => {
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const room = new MockRoom(memberUserIdAlice);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
@ -186,23 +151,7 @@ test("Can close search", async () => {
|
|||||||
<TestComponent rtcSession={rtcSession} />,
|
<TestComponent rtcSession={rtcSession} />,
|
||||||
);
|
);
|
||||||
await user.click(getByLabelText("action.raise_hand_or_send_reaction"));
|
await user.click(getByLabelText("action.raise_hand_or_send_reaction"));
|
||||||
await user.click(getByLabelText("action.open_search"));
|
await user.click(getByLabelText("action.show_more"));
|
||||||
await user.click(getByLabelText("action.close_search"));
|
await user.click(getByLabelText("action.show_less"));
|
||||||
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" });
|
|
||||||
});
|
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -5,24 +5,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Button as CpdButton, Tooltip, Alert } from "@vector-im/compound-web";
|
||||||
import {
|
import {
|
||||||
Button as CpdButton,
|
|
||||||
Tooltip,
|
|
||||||
Search,
|
|
||||||
Form,
|
|
||||||
Alert,
|
|
||||||
} from "@vector-im/compound-web";
|
|
||||||
import {
|
|
||||||
SearchIcon,
|
|
||||||
CloseIcon,
|
|
||||||
RaisedHandSolidIcon,
|
RaisedHandSolidIcon,
|
||||||
ReactionIcon,
|
ReactionIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
ChevronUpIcon,
|
||||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
import {
|
import {
|
||||||
ChangeEventHandler,
|
|
||||||
ComponentPropsWithoutRef,
|
ComponentPropsWithoutRef,
|
||||||
FC,
|
FC,
|
||||||
KeyboardEventHandler,
|
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -76,43 +68,11 @@ export function ReactionPopupMenu({
|
|||||||
canReact: boolean;
|
canReact: boolean;
|
||||||
}): ReactNode {
|
}): ReactNode {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [searchText, setSearchText] = useState("");
|
const [isFullyExpanded, setExpanded] = useState(false);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
|
||||||
const onSearch = useCallback<ChangeEventHandler<HTMLInputElement>>((ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
setSearchText(ev.target.value.trim().toLocaleLowerCase());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const filteredReactionSet = useMemo(
|
const filteredReactionSet = useMemo(
|
||||||
() =>
|
() => (isFullyExpanded ? ReactionSet : ReactionSet.slice(0, 5)),
|
||||||
ReactionSet.filter(
|
[isFullyExpanded],
|
||||||
(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],
|
|
||||||
);
|
);
|
||||||
const label = isHandRaised
|
const label = isHandRaised
|
||||||
? t("action.lower_hand", { keyboardShortcut: "H" })
|
? t("action.lower_hand", { keyboardShortcut: "H" })
|
||||||
@ -143,36 +103,15 @@ export function ReactionPopupMenu({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</section>
|
</section>
|
||||||
<div className={styles.verticalSeperator} />
|
<div className={styles.verticalSeperator} />
|
||||||
<section>
|
<section className={styles.reactionsMenuSection}>
|
||||||
{isSearching ? (
|
<menu
|
||||||
<>
|
className={classNames(
|
||||||
<Form.Root className={styles.searchForm}>
|
isFullyExpanded && styles.reactionsMenuExpanded,
|
||||||
<Search
|
styles.reactionsMenu,
|
||||||
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}>
|
|
||||||
{filteredReactionSet.map((reaction, index) => (
|
{filteredReactionSet.map((reaction, index) => (
|
||||||
<li className={styles.reactionPopupMenuItem} key={reaction.name}>
|
<li key={reaction.name}>
|
||||||
{/* Show the keyboard key assigned to the reaction */}
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={
|
label={
|
||||||
index < ReactionsRowSize
|
index < ReactionsRowSize
|
||||||
@ -198,21 +137,23 @@ export function ReactionPopupMenu({
|
|||||||
))}
|
))}
|
||||||
</menu>
|
</menu>
|
||||||
</section>
|
</section>
|
||||||
{!isSearching ? (
|
<section style={{ marginLeft: "var(--cpd-separator-spacing)" }}>
|
||||||
<section style={{ marginLeft: "var(--cpd-separator-spacing)" }}>
|
<Tooltip
|
||||||
<li key="search" className={styles.reactionPopupMenuItem}>
|
label={
|
||||||
<Tooltip label={t("common.search")}>
|
isFullyExpanded ? t("action.show_less") : t("action.show_more")
|
||||||
<CpdButton
|
}
|
||||||
iconOnly
|
>
|
||||||
aria-label={t("action.open_search")}
|
<CpdButton
|
||||||
Icon={SearchIcon}
|
iconOnly
|
||||||
kind="tertiary"
|
aria-label={
|
||||||
onClick={() => setIsSearching(true)}
|
isFullyExpanded ? t("action.show_less") : t("action.show_more")
|
||||||
/>
|
}
|
||||||
</Tooltip>
|
Icon={isFullyExpanded ? ChevronUpIcon : ChevronDownIcon}
|
||||||
</li>
|
kind="tertiary"
|
||||||
</section>
|
onClick={() => setExpanded(!isFullyExpanded)}
|
||||||
) : null}
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -289,12 +230,13 @@ export function ReactionToggleButton({
|
|||||||
title={t("action.pick_reaction")}
|
title={t("action.pick_reaction")}
|
||||||
hideHeader
|
hideHeader
|
||||||
classNameModal={styles.reactionPopupMenuModal}
|
classNameModal={styles.reactionPopupMenuModal}
|
||||||
|
className={styles.reactionPopupMenuRoot}
|
||||||
onDismiss={() => setShowReactionsMenu(false)}
|
onDismiss={() => setShowReactionsMenu(false)}
|
||||||
>
|
>
|
||||||
<ReactionPopupMenu
|
<ReactionPopupMenu
|
||||||
errorText={errorText}
|
errorText={errorText}
|
||||||
isHandRaised={isHandRaised}
|
isHandRaised={isHandRaised}
|
||||||
canReact={canReact}
|
canReact={!busy && canReact}
|
||||||
sendReaction={(reaction) => void sendRelation(reaction)}
|
sendReaction={(reaction) => void sendRelation(reaction)}
|
||||||
toggleRaisedHand={wrappedToggleRaisedHand}
|
toggleRaisedHand={wrappedToggleRaisedHand}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`Can close search 1`] = `
|
exports[`Can close reaction dialog 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
data-aria-hidden="true"
|
data-aria-hidden="true"
|
||||||
@ -10,7 +10,7 @@ exports[`Can close search 1`] = `
|
|||||||
aria-expanded="true"
|
aria-expanded="true"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-label="action.raise_hand_or_send_reaction"
|
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"
|
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@ -36,16 +36,19 @@ exports[`Can close search 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Can close search with the escape key 1`] = `
|
exports[`Can fully expand emoji picker 1`] = `
|
||||||
<div>
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
data-aria-hidden="true"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-expanded="false"
|
aria-expanded="true"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-label="action.raise_hand_or_send_reaction"
|
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"
|
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
data-kind="secondary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@ -76,7 +79,7 @@ exports[`Can lower hand 1`] = `
|
|||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-label="action.raise_hand_or_send_reaction"
|
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"
|
class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@ -142,7 +145,7 @@ exports[`Can raise hand 1`] = `
|
|||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-label="action.raise_hand_or_send_reaction"
|
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"
|
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
data-kind="secondary"
|
data-kind="secondary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@ -167,75 +170,3 @@ exports[`Can raise hand 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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();
|
const internalInstance = new Config();
|
||||||
Config.internalInstance = internalInstance;
|
Config.internalInstance = internalInstance;
|
||||||
|
|
||||||
Config.internalInstance.initPromise = downloadConfig(
|
Config.internalInstance.initPromise = downloadConfig("/config.json").then(
|
||||||
"../config.json",
|
(config) => {
|
||||||
).then((config) => {
|
internalInstance.config = merge({}, DEFAULT_CONFIG, config);
|
||||||
internalInstance.config = merge({}, DEFAULT_CONFIG, config);
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
return Config.internalInstance.initPromise;
|
return Config.internalInstance.initPromise;
|
||||||
}
|
}
|
||||||
@ -74,11 +74,7 @@ async function downloadConfig(
|
|||||||
configJsonFilename: string,
|
configJsonFilename: string,
|
||||||
): Promise<ConfigOptions> {
|
): Promise<ConfigOptions> {
|
||||||
const url = new URL(configJsonFilename, window.location.href);
|
const url = new URL(configJsonFilename, window.location.href);
|
||||||
url.searchParams.set("cachebuster", Date.now().toString());
|
const res = await fetch(url);
|
||||||
const res = await fetch(url, {
|
|
||||||
cache: "no-cache",
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok || res.status === 404 || res.status === 0) {
|
if (!res.ok || res.status === 404 || res.status === 0) {
|
||||||
// Lack of a config isn't an error, we should just use the defaults.
|
// 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 {
|
.content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
align-self: center;
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatarFieldRow {
|
.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