Skip to content

Commit

Permalink
refactor: break out common dependencies
Browse files Browse the repository at this point in the history
don't know how much this affects performance?
  • Loading branch information
6lr61 committed Sep 18, 2024
1 parent 0f3f465 commit f46f6bf
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 141 deletions.
4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -23,7 +23,7 @@ export default function App() {
<p className="bg-pink-50">Hello: {authState.user.login}</p>
<article>
<h2>Chat Messages:</h2>
<Chat />
<Messages />
</article>
</section>
)}
Expand Down
6 changes: 3 additions & 3 deletions src/components/BadgeList.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,6 +8,7 @@ interface Props {
/** Months subscribed */
info: string;
}[];
twitchBadges: ReturnType<typeof useBadges>;
}

function makeKey(setId: string, id: string): `${string}/${string}` {
Expand All @@ -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;
}
Expand Down
26 changes: 0 additions & 26 deletions src/components/Chat.tsx

This file was deleted.

85 changes: 0 additions & 85 deletions src/components/Message.tsx

This file was deleted.

128 changes: 128 additions & 0 deletions src/components/Messages.tsx
Original file line number Diff line number Diff line change
@@ -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 <TextSegment key={key} text={fragment.text} />;
case "emote":
return <TwitchEmote key={key} fragment={fragment} />;
case "mention":
return <MentionSegment key={key} text={fragment.text} />;
case "7tv-emote":
case "bttv-emote":
return (
<img
key={key}
className="relative inline-block -my-2"
{...fragment.small}
/>
);
default:
return;
}
}

export default function Messages(): React.ReactElement {
const { authState } = useContext(AuthContext);
const messages = useTwitchChat();
const ref = useRef<HTMLDivElement>(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 (
<section ref={ref} className="h-[600px] justify-end overflow-y-auto">
<article className="flex flex-col gap-1">
{messages.map(
({
chatter_user_login,
chatter_user_name,
color,
badges,
timestamp,
reply,
message,
}) => (
<article className="flex flex-row gap-1">
<ProfilePicture login={chatter_user_login} />
<section className="flex-1 overflow-hidden rounded-md">
<header
className="flex px-2 gap-2 items-center bg-black/25"
style={{ backgroundColor: colorToRgba(color) }}
>
<BadgeList badges={badges} twitchBadges={twitchBadges} />
<UserName
login={chatter_user_login}
displayName={chatter_user_name}
/>
<Pronoun login={chatter_user_login} />
<ElapsedTime startingDate={timestamp} />
</header>
{reply && <Reply message={reply} />}
<section className="h-full bg-slate-800 break-words px-2 py-1">
{parseFragments(message.fragments)
.slice(reply ? 1 : 0) // Drop the first mention fragment
.map((fragment, index) =>
fragmentToComponent(fragment, index)
)}
</section>
</section>
</article>
)
)}
</article>
</section>
);
}
18 changes: 7 additions & 11 deletions src/components/UserName.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<p className="overflow-hidden text-ellipsis">
<span className="font-bold">{message.chatter_user_name}</span>
{hasLocalizedName(message) && (
<span className="font-normal">({message.chatter_user_login})</span>
<span className="font-bold">{displayName}</span>
{displayName.toLowerCase() !== login && (
<span className="font-normal">({login})</span>
)}
</p>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, BetterTTVEmoteFragment | SevenTVEmoteFragment>;

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) => {
Expand All @@ -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({
Expand Down

0 comments on commit f46f6bf

Please sign in to comment.