Linting and general stability improvements.

This commit is contained in:
Half-Shot
2024-11-01 17:22:45 +00:00
parent dfe9569720
commit e3c23fa2cf
11 changed files with 154 additions and 58 deletions

View File

@@ -21,7 +21,11 @@
.reactionPopupMenuItem {
list-style: none;
gap: 1em;
margin-left: var(--cpd-separator-spacing);
}
.reactionPopupMenuItem:first-child {
margin-left: 0;
}
.reactionButton {
@@ -37,3 +41,13 @@
margin-left: var(--cpd-separator-spacing);
margin-right: var(--cpd-separator-spacing);
}
.searchForm {
display: flex;
flex-direction: row;
gap: var(--cpd-separator-spacing);
}
.searchForm > label {
flex: auto;
}

View File

@@ -12,7 +12,11 @@ import {
Search,
Form,
} from "@vector-im/compound-web";
import { ReactionIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import {
SearchIcon,
ReactionIcon,
CloseIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import {
ChangeEventHandler,
ComponentPropsWithoutRef,
@@ -35,6 +39,7 @@ import {
ECallReactionEventContent,
ReactionOption,
ReactionSet,
ElementCallReactionEventType,
} from "../reactions";
interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> {
@@ -69,6 +74,7 @@ export function ReactionPopupMenu({
}): 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());
@@ -78,11 +84,14 @@ export function ReactionPopupMenu({
() =>
ReactionSet.filter(
(reaction) =>
reaction.name.startsWith(searchText) ||
reaction.alias?.some((a) => a.startsWith(searchText)),
!isSearching ||
(!!searchText &&
(reaction.name.startsWith(searchText) ||
reaction.alias?.some((a) => a.startsWith(searchText)))),
).slice(0, 6),
[searchText],
[searchText, isSearching],
);
return (
<div className={styles.reactionPopupMenu}>
<section className={styles.handRaiseSection}>
@@ -99,14 +108,28 @@ export function ReactionPopupMenu({
</section>
<div className={styles.verticalSeperator} />
<section>
<Form.Root onSubmit={(e) => e.preventDefault()}>
<Search
value={searchText}
name="reactionSearch"
onChange={onSearch}
/>
</Form.Root>
<Separator />
{isSearching ? (
<>
<Form.Root
className={styles.searchForm}
onSubmit={(e) => e.preventDefault()}
>
<Search
value={searchText}
name="reactionSearch"
placeholder="Search reactions…"
onChange={onSearch}
/>
<CpdButton
Icon={CloseIcon}
size="sm"
kind="destructive"
onClick={() => setIsSearching(false)}
/>
</Form.Root>
<Separator />
</>
) : null}
<menu>
{filteredReactionSet.map((reaction) => (
<li className={styles.reactionPopupMenuItem}>
@@ -123,21 +146,33 @@ export function ReactionPopupMenu({
</Tooltip>
</li>
))}
{!isSearching ? (
<li key="search" className={styles.reactionPopupMenuItem}>
<Tooltip label="Search">
<CpdButton
iconOnly
Icon={SearchIcon}
kind="tertiary"
onClick={() => setIsSearching(true)}
/>
</Tooltip>
</li>
) : null}
</menu>
</section>
</div>
);
}
interface RaisedHandToggleButtonProps {
interface ReactionToggleButtonProps {
rtcSession: MatrixRTCSession;
client: MatrixClient;
}
export function RaiseHandToggleButton({
export function ReactionToggleButton({
client,
rtcSession,
}: RaisedHandToggleButtonProps): ReactNode {
}: ReactionToggleButtonProps): ReactNode {
const { raisedHands, myReactionId, reactions } = useReactions();
const [busy, setBusy] = useState(false);
const userId = client.getUserId()!;
@@ -161,7 +196,7 @@ export function RaiseHandToggleButton({
await client.sendEvent(
rtcSession.room.roomId,
null,
"io.element.call.reaction",
ElementCallReactionEventType,
{
"m.relates_to": {
rel_type: RelationType.Reference,
@@ -171,7 +206,7 @@ export function RaiseHandToggleButton({
name: reaction.name,
} as ECallReactionEventContent,
);
setShowReactionsMenu(false);
// Do NOT close the menu after this.
} catch (ex) {
logger.error("Failed to send reaction", ex);
} finally {
@@ -222,6 +257,7 @@ export function RaiseHandToggleButton({
logger.error("Failed to send reaction event", ex);
} finally {
setBusy(false);
setShowReactionsMenu(false);
}
}
};

View File

@@ -7,4 +7,4 @@ Please see LICENSE in the repository root for full details.
export * from "./Button";
export * from "./LinkButton";
export * from "./RaisedHandToggleButton";
export * from "./ReactionToggleButton";

View File

@@ -7,17 +7,22 @@ Please see LICENSE in the repository root for full details.
import { RelationType } from "matrix-js-sdk/src/types";
import catSoundOgg from "../sound/reactions/cat.mp3?url";
import catSoundOgg from "../sound/reactions/cat.ogg?url";
import catSoundMp3 from "../sound/reactions/cat.mp3?url";
import cricketsSoundOgg from "../sound/reactions/crickets.mp3?url";
import clapSoundOgg from "../sound/reactions/clap.ogg?url";
import clapSoundMp3 from "../sound/reactions/clap.mp3?url";
import cricketsSoundOgg from "../sound/reactions/crickets.ogg?url";
import cricketsSoundMp3 from "../sound/reactions/crickets.mp3?url";
import dogSoundOgg from "../sound/reactions/dog.ogg?url";
import dogSoundMp3 from "../sound/reactions/dog.mp3?url";
import genericSoundOgg from "../sound/reactions/generic.mp3?url";
import genericSoundOgg from "../sound/reactions/generic.ogg?url";
import genericSoundMp3 from "../sound/reactions/generic.mp3?url";
import lightbulbSoundOgg from "../sound/reactions/lightbulb.mp3?url";
import lightbulbSoundOgg from "../sound/reactions/lightbulb.ogg?url";
import lightbulbSoundMp3 from "../sound/reactions/lightbulb.mp3?url";
import partySoundOgg from "../sound/reactions/party.ogg?url";
import partySoundMp3 from "../sound/reactions/party.mp3?url";
export const ElementCallReactionEventType = "io.element.call.reaction";
export interface ReactionOption {
emoji: string;
@@ -47,21 +52,30 @@ export const GenericReaction: ReactionOption = {
},
};
// The first 6 reactions are always visible.
export const ReactionSet: ReactionOption[] = [
{
emoji: "👍",
name: "thumbsup",
alias: ["+1", "yes", "thumbs up"],
},
{
emoji: "👎",
name: "thumbsdown",
alias: ["-1", "no", "thumbs no"],
},
{
emoji: "🎉",
name: "party",
alias: ["hurray", "success"],
sound: {
ogg: partySoundOgg,
mp3: partySoundMp3,
},
},
{
emoji: "👏",
name: "clapping",
alias: ["celebrate", "success"],
sound: {
ogg: clapSoundOgg,
mp3: clapSoundMp3,
},
},
{
emoji: "🐶",
@@ -72,15 +86,6 @@ export const ReactionSet: ReactionOption[] = [
mp3: dogSoundMp3,
},
},
{
emoji: "🦗",
name: "crickets",
alias: ["awkward", "silence"],
sound: {
ogg: cricketsSoundOgg,
mp3: cricketsSoundMp3,
},
},
{
emoji: "🐱",
name: "cat",
@@ -99,6 +104,20 @@ export const ReactionSet: ReactionOption[] = [
mp3: lightbulbSoundMp3,
},
},
{
emoji: "🦗",
name: "crickets",
alias: ["awkward", "silence"],
sound: {
ogg: cricketsSoundOgg,
mp3: cricketsSoundMp3,
},
},
{
emoji: "👎",
name: "thumbsdown",
alias: ["-1", "no", "thumbs no"],
},
{
emoji: "😵‍💫",
name: "dizzy",
@@ -109,4 +128,14 @@ export const ReactionSet: ReactionOption[] = [
name: "ok",
alias: ["okay", "cool"],
},
{
emoji: "🥰",
name: "heart",
alias: ["heart", "love", "smiling"],
},
{
emoji: "😄",
name: "laugh",
alias: ["giggle", "joy", "smiling"],
},
];

View File

@@ -147,11 +147,11 @@ Please see LICENSE in the repository root for full details.
.floatingReaction {
position: relative;
display: inline;
z-index: 2;
font-size: 32pt;
animation-duration: 5s;
animation-duration: 4s;
animation-name: reaction-up;
bottom: 0;
width: fit-content;
}

View File

@@ -41,7 +41,7 @@ import {
VideoButton,
ShareScreenButton,
SettingsButton,
RaiseHandToggleButton,
ReactionToggleButton,
} from "../button";
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
import { useUrlParams } from "../UrlParams";
@@ -83,6 +83,7 @@ import { makeSpotlightPortraitLayout } from "../grid/SpotlightPortraitLayout";
import { ReactionsProvider, useReactions } from "../useReactions";
import handSoundOgg from "../sound/raise_hand.ogg?url";
import handSoundMp3 from "../sound/raise_hand.mp3?url";
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
@@ -182,12 +183,6 @@ export const InCallView: FC<InCallViewProps> = ({
);
const previousRaisedHandCount = useDeferredValue(raisedHandCount);
// TODO: This may need to ensure we don't change the value if a duplicate reaction comes down.
const reactionsSet = useMemo(
() => [...new Set([...Object.values(reactions)])],
[reactions],
);
const reactionsIcons = useMemo(
() =>
Object.entries(reactions).map(([sender, { emoji }]) => ({
@@ -558,7 +553,7 @@ export const InCallView: FC<InCallViewProps> = ({
}
if (supportsReactions) {
buttons.push(
<RaiseHandToggleButton
<ReactionToggleButton
client={client}
rtcSession={rtcSession}
key="4"
@@ -650,17 +645,7 @@ export const InCallView: FC<InCallViewProps> = ({
<source src={handSoundOgg} type="audio/ogg; codecs=vorbis" />
<source src={handSoundMp3} type="audio/mpeg" />
</audio>
{reactionsSet.map(
(r) =>
r.sound && (
<audio key={r.name} autoPlay hidden>
<source src={r.sound.ogg} type="audio/ogg; codecs=vorbis" />
{r.sound.mp3 ? (
<source src={r.sound.mp3} type="audio/mpeg" />
) : null}
</audio>
),
)}
<ReactionsAudioRenderer />
{reactionsIcons.map(({ sender, emoji, startX }) => (
<span
style={{ left: `${startX}vw` }}

View File

@@ -0,0 +1,32 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { ReactNode } from "react";
import { useReactions } from "../useReactions";
export function ReactionsAudioRenderer(): ReactNode {
const { reactions } = useReactions();
// TODO: This may need to ensure we don't change the value if a duplicate reaction comes down.
const expectedReactions = [...new Set([...Object.values(reactions)])];
return (
<>
{expectedReactions.map(
(r) =>
r.sound && (
<audio key={r.name} autoPlay hidden>
<source src={r.sound.ogg} type="audio/ogg; codecs=vorbis" />
{r.sound.mp3 ? (
<source src={r.sound.mp3} type="audio/mpeg" />
) : null}
</audio>
),
)}
</>
);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.