From f46f6bf3c027c1e116953a23cc672f86cb8510ce Mon Sep 17 00:00:00 2001
From: Adrian <107351903+6lr61@users.noreply.github.com>
Date: Wed, 18 Sep 2024 15:48:36 +0200
Subject: [PATCH] refactor: break out common dependencies
don't know how much this affects performance?
---
src/App.tsx | 4 +-
src/components/BadgeList.tsx | 6 +-
src/components/Chat.tsx | 26 ----
src/components/Message.tsx | 85 ------------
src/components/Messages.tsx | 128 ++++++++++++++++++
src/components/UserName.tsx | 18 +--
.../findThirdPartyEmotes.ts} | 23 ++--
7 files changed, 149 insertions(+), 141 deletions(-)
delete mode 100644 src/components/Chat.tsx
delete mode 100644 src/components/Message.tsx
create mode 100644 src/components/Messages.tsx
rename src/{hooks/useThirdPartyEmotes.ts => utils/findThirdPartyEmotes.ts} (56%)
diff --git a/src/App.tsx b/src/App.tsx
index bd1cb28..ce21243 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,7 +1,7 @@
import { useContext, useMemo } from "react";
import { AuthContext } from "./contexts/auth-state/AuthContext";
import LoginButton from "./components/LoginButton";
-import Chat from "./components/Chat";
+import Messages from "./components/Messages";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
export default function App() {
@@ -23,7 +23,7 @@ export default function App() {
Hello: {authState.user.login}
Chat Messages:
-
+
)}
diff --git a/src/components/BadgeList.tsx b/src/components/BadgeList.tsx
index b4ded3a..01da5c1 100644
--- a/src/components/BadgeList.tsx
+++ b/src/components/BadgeList.tsx
@@ -1,4 +1,4 @@
-import { useBadges } from "../hooks/useBadges";
+import type { useBadges } from "../hooks/useBadges";
interface Props {
// FIXME: Derive this from a single point of truth
@@ -8,6 +8,7 @@ interface Props {
/** Months subscribed */
info: string;
}[];
+ twitchBadges: ReturnType;
}
function makeKey(setId: string, id: string): `${string}/${string}` {
@@ -16,9 +17,8 @@ function makeKey(setId: string, id: string): `${string}/${string}` {
export default function BadgeList({
badges,
+ twitchBadges,
}: Props): React.ReactElement | undefined {
- const twitchBadges = useBadges();
-
if (badges.length === 0) {
return;
}
diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx
deleted file mode 100644
index c89c4be..0000000
--- a/src/components/Chat.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { useEffect, useRef } from "react";
-import { useTwitchChat } from "../hooks/useTwitchChat";
-import Message from "./Message";
-
-export default function Chat(): React.ReactElement {
- const messages = useTwitchChat();
- const ref = useRef(null);
-
- useEffect(() => {
- if (!ref.current) {
- return;
- }
-
- ref.current.scroll(0, ref.current.scrollHeight);
- }, [messages]);
-
- return (
-
-
- {messages.map((message) => (
-
- ))}
-
-
- );
-}
diff --git a/src/components/Message.tsx b/src/components/Message.tsx
deleted file mode 100644
index e0a4dc2..0000000
--- a/src/components/Message.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import {
- useThirdPartyEmotes,
- type Fragment,
-} from "../hooks/useThirdPartyEmotes";
-import type { ChatMessage } from "../hooks/useTwitchChat";
-import BadgeList from "./BadgeList";
-import ElapsedTime from "./ElapsedTime";
-import MentionSegment from "./MentionSegment";
-import ProfilePicture from "./ProfilePicture";
-import Pronoun from "./Pronoun";
-import Reply from "./Reply";
-import TextSegment from "./TextSegment";
-import TwitchEmote from "./TwitchEmote";
-import UserName from "./UserName";
-
-interface Props {
- message: ChatMessage;
-}
-
-function colorToRgba(color?: string): string | undefined {
- if (!color) {
- return;
- }
-
- // #aabbcc
- const red = Number.parseInt(color.slice(1, 3), 16).toString();
- const green = Number.parseInt(color.slice(3, 5), 16).toString();
- const blue = Number.parseInt(color.slice(5), 16).toString();
-
- return `rgba(${red}, ${green}, ${blue}, 0.25)`;
-}
-
-function fragmentToComponent(
- fragment: Fragment,
- index: number
-): React.ReactElement | undefined {
- const key = `fragment-${index.toString()}`;
-
- switch (fragment.type) {
- case "text":
- return ;
- case "emote":
- return ;
- case "mention":
- return ;
- case "7tv-emote":
- case "bttv-emote":
- return (
-
- );
- default:
- return;
- }
-}
-
-export default function Message({ message }: Props): React.ReactElement {
- const fragments = useThirdPartyEmotes(message.message.fragments);
-
- return (
-
-
-
-
- {message.reply && }
-
- {fragments
- .slice(message.reply ? 1 : 0) // Drop the first mention fragment
- .map((fragment, index) => fragmentToComponent(fragment, index))}
-
-
-
- );
-}
diff --git a/src/components/Messages.tsx b/src/components/Messages.tsx
new file mode 100644
index 0000000..4960631
--- /dev/null
+++ b/src/components/Messages.tsx
@@ -0,0 +1,128 @@
+import { useCallback, useContext, useEffect, useMemo, useRef } from "react";
+import { useTwitchChat } from "../hooks/useTwitchChat";
+import ProfilePicture from "./ProfilePicture";
+import BadgeList from "./BadgeList";
+import UserName from "./UserName";
+import Pronoun from "./Pronoun";
+import ElapsedTime from "./ElapsedTime";
+import Reply from "./Reply";
+import TextSegment from "./TextSegment";
+import TwitchEmote from "./TwitchEmote";
+import MentionSegment from "./MentionSegment";
+import {
+ findThirdPartyEmotes,
+ type Fragment,
+} from "../utils/findThirdPartyEmotes";
+import { useBadges } from "../hooks/useBadges";
+import type { ChatFragment } from "../utils/event-sub/events/chat/message";
+import { useBttvEmotes } from "../hooks/useBttvEmotes";
+import { useSevenTvEmotes } from "../hooks/useSevenTvEmotes";
+import { AuthContext } from "../contexts/auth-state/AuthContext";
+
+function colorToRgba(color?: string): string | undefined {
+ if (!color) {
+ return;
+ }
+
+ const red = Number.parseInt(color.slice(1, 3), 16).toString();
+ const green = Number.parseInt(color.slice(3, 5), 16).toString();
+ const blue = Number.parseInt(color.slice(5), 16).toString();
+
+ return `rgba(${red}, ${green}, ${blue}, 0.25)`;
+}
+
+function fragmentToComponent(
+ fragment: Fragment,
+ index: number
+): React.ReactElement | undefined {
+ const key = `fragment-${index.toString()}`;
+
+ switch (fragment.type) {
+ case "text":
+ return ;
+ case "emote":
+ return ;
+ case "mention":
+ return ;
+ case "7tv-emote":
+ case "bttv-emote":
+ return (
+
+ );
+ default:
+ return;
+ }
+}
+
+export default function Messages(): React.ReactElement {
+ const { authState } = useContext(AuthContext);
+ const messages = useTwitchChat();
+ const ref = useRef(null);
+ const twitchBadges = useBadges();
+ const bttvEmotes = useBttvEmotes(authState?.user.id);
+ const sevenTvEmotes = useSevenTvEmotes(authState?.user.id);
+ const emotes = useMemo(
+ () => ({ ...bttvEmotes, ...sevenTvEmotes }),
+ [bttvEmotes, sevenTvEmotes]
+ );
+ const parseFragments = useCallback(
+ (fragments: ChatFragment[]) => findThirdPartyEmotes(fragments, emotes),
+ [emotes]
+ );
+
+ useEffect(() => {
+ if (!ref.current) {
+ return;
+ }
+
+ ref.current.scroll(0, ref.current.scrollHeight);
+ }, [messages]);
+
+ return (
+
+
+ {messages.map(
+ ({
+ chatter_user_login,
+ chatter_user_name,
+ color,
+ badges,
+ timestamp,
+ reply,
+ message,
+ }) => (
+
+
+
+
+ {reply && }
+
+ {parseFragments(message.fragments)
+ .slice(reply ? 1 : 0) // Drop the first mention fragment
+ .map((fragment, index) =>
+ fragmentToComponent(fragment, index)
+ )}
+
+
+
+ )
+ )}
+
+
+ );
+}
diff --git a/src/components/UserName.tsx b/src/components/UserName.tsx
index 33da112..5404ae2 100644
--- a/src/components/UserName.tsx
+++ b/src/components/UserName.tsx
@@ -1,19 +1,15 @@
-import type { ChatMessage } from "../hooks/useTwitchChat";
-
-function hasLocalizedName(message: ChatMessage): boolean {
- return message.chatter_user_name.toLowerCase() !== message.chatter_user_login;
-}
-
export default function UserName({
- message,
+ displayName,
+ login,
}: {
- message: ChatMessage;
+ displayName: string;
+ login: string;
}): React.ReactElement {
return (
- {message.chatter_user_name}
- {hasLocalizedName(message) && (
- ({message.chatter_user_login})
+ {displayName}
+ {displayName.toLowerCase() !== login && (
+ ({login})
)}
);
diff --git a/src/hooks/useThirdPartyEmotes.ts b/src/utils/findThirdPartyEmotes.ts
similarity index 56%
rename from src/hooks/useThirdPartyEmotes.ts
rename to src/utils/findThirdPartyEmotes.ts
index f1a065b..d8c595f 100644
--- a/src/hooks/useThirdPartyEmotes.ts
+++ b/src/utils/findThirdPartyEmotes.ts
@@ -1,22 +1,17 @@
-import { useContext } from "react";
-import { AuthContext } from "../contexts/auth-state/AuthContext";
-import { useBttvEmotes, type BetterTTVEmoteFragment } from "./useBttvEmotes";
-import {
- useSevenTvEmotes,
- type SevenTVEmoteFragment,
-} from "./useSevenTvEmotes";
-import type { ChatFragment } from "../utils/event-sub/events/chat/message";
+import { type BetterTTVEmoteFragment } from "../hooks/useBttvEmotes";
+import { type SevenTVEmoteFragment } from "../hooks/useSevenTvEmotes";
+import type { ChatFragment } from "./event-sub/events/chat/message";
export type Fragment =
| ChatFragment
| BetterTTVEmoteFragment
| SevenTVEmoteFragment;
+type Emotes = Record;
-export function useThirdPartyEmotes(fragments: ChatFragment[]): Fragment[] {
- const { authState } = useContext(AuthContext);
- const bttvEmotes = useBttvEmotes(authState?.user.id);
- const sevenTvEmotes = useSevenTvEmotes(authState?.user.id);
- const emotes = { ...bttvEmotes, ...sevenTvEmotes };
+export function findThirdPartyEmotes(
+ fragments: ChatFragment[],
+ emotes: Emotes
+): Fragment[] {
const names = Object.keys(emotes);
return fragments.flatMap((fragment) => {
@@ -28,9 +23,9 @@ export function useThirdPartyEmotes(fragments: ChatFragment[]): Fragment[] {
({ 0: match }) => names.includes(match)
);
- /// Some text Kappa Kappa and such
const subFragments: Fragment[] = [];
let start = 0;
+
for (const { 0: name, index } of found) {
if (start < index) {
subFragments.push({