Skip to content

Commit

Permalink
feat: render chat messages
Browse files Browse the repository at this point in the history
  • Loading branch information
6lr61 committed Sep 11, 2024
1 parent 4dd2ef9 commit 096d4ae
Show file tree
Hide file tree
Showing 19 changed files with 789 additions and 11 deletions.
26 changes: 19 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,36 @@ import { useContext } from "react";
import { AuthStateContext } from "./contexts/auth-state/AuthStateContext";
import LoginButton from "./components/LoginButton";
import "./App.css";
import { useEventSub } from "./hooks/useEventSub";
import { useTwitchChat } from "./hooks/useTwitchChat";
import TwitchBadgeProvider from "./contexts/badges/TwitchBadgeProvider";
import Message from "./components/Message";

export default function App() {
const authContext = useContext(AuthStateContext);
const { lastMessage } = useEventSub("channel.chat.message", {
broadcaster_user_id: authContext?.authState?.user.id,
user_id: authContext?.authState?.user.id,
});
const messages = useTwitchChat();

if (!authContext) {
return <p>Missing AuthStateContext provider?</p>;
}

return (
<>
<p>Hello: {authContext.authState?.user.login}</p>
<p>Last message: {JSON.stringify(lastMessage)}</p>
<LoginButton />
{authContext.authState && (
<section>
<p>Hello: {authContext.authState.user.login}</p>
<article>
<h2>Chat Messages:</h2>
<TwitchBadgeProvider>
<div>
{messages.map((message) => (
<Message key={message.message_id} message={message} />
))}
</div>
</TwitchBadgeProvider>
</article>
</section>
)}
</>
);
}
37 changes: 37 additions & 0 deletions src/components/BadgeList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useContext } from "react";
import { TwitchBadgeConext } from "../contexts/badges/TwitchBadgeContext";

interface Props {
// FIXME: Derive this from a single point of truth
badges: {
set_id: string;
id: string;
/** Months subscribed */
info: string;
}[];
}

function makeKey(setId: string, id: string): `${string}/${string}` {
return `${setId}/${id}`;
}

export default function BadgeList({
badges,
}: Props): React.ReactElement | undefined {
const twitchBadges = useContext(TwitchBadgeConext);

if (!twitchBadges || badges.length === 0) {
return;
}

return (
<div className="badges">
{badges.map(({ set_id, id }) => (
<img
key={makeKey(set_id, id)}
src={twitchBadges.get(makeKey(set_id, id))?.image_url_1x}
/>
))}
</div>
);
}
34 changes: 34 additions & 0 deletions src/components/ElapsedTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { type HTMLProps, useEffect, useState } from "react";

const UNA_MINUTA = 60_000;
const MINUTE_IN_MILLIS = 60_000;

function passedTimeMinutes(startingDate: Date): number {
return Math.floor((Date.now() - startingDate.getTime()) / MINUTE_IN_MILLIS);
}

export default function ElapsedTime({
startingDate,
}: {
startingDate: Date;
} & HTMLProps<HTMLSpanElement>): React.ReactElement {
const [minutes, setMinutes] = useState(passedTimeMinutes(startingDate));

useEffect(() => {
const timer = setInterval(() => {
setMinutes(() => passedTimeMinutes(startingDate));
}, UNA_MINUTA);

return () => {
clearInterval(timer);
};
}, [startingDate]);

return (
<>
<span className="timer">
{minutes > 0 ? `⧗ ${minutes.toString()}m` : "just now!"}
</span>
</>
);
}
25 changes: 25 additions & 0 deletions src/components/MentionSegment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useContext } from "react";
import { AuthStateContext } from "../contexts/auth-state/AuthStateContext";

interface MentionProps {
text: string;
}

export default function MentionSegment({
text,
}: MentionProps): React.ReactElement {
const mentioned = text.replace("@", "").toLocaleLowerCase();
const authStateContext = useContext(AuthStateContext);

return (
<span
className={
mentioned === authStateContext?.authState?.user.login
? "streamer-mention"
: "chatter-mention"
}
>
{text}
</span>
);
}
239 changes: 239 additions & 0 deletions src/components/Message.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
.message {
display: flex;
flex-direction: row;
margin: 0 0.3rem 0.3rem 0.3rem;

&.first-message .profile-picture,
&.first-message .container {
font-weight: bold;
box-shadow: 0rem 0rem 1rem 0.5rem
var(--vscode-list-focusHighlightForeground);
}

&.highlighted .profile-picture,
&.highlighted .container {
border-color: var(--vscode-list-focusHighlightForeground);
border-style: solid;
border-width: 3px;
font-weight: bold;
}

.profile-picture {
aspect-ratio: 1;
border-radius: 12px;
height: 58px;
margin-right: 0.3rem;

img {
border-radius: 10px;
position: relative;
}
}

.container {
border-radius: 0.3rem;
flex: 1;
overflow: hidden;
}

header {
align-items: center;
background-color: rgba(0, 0, 0, 0.25);
border-radius: 0.3rem 0.3rem 0rem 0rem;
display: flex;
font-weight: bold;
height: 1.5rem;
padding: 0 0.5rem;

.badges {
display: inline-block;
height: 0.8rem;
text-wrap: nowrap;

img {
margin-right: 0.2rem;
}
}

p {
flex-grow: 1;
height: 100%;
line-height: 1.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

.display-name {
font-weight: bold;
}

.username,
.pronoun {
font-weight: normal;
margin-left: 0.3rem;
}
}

.timer {
color: var(--vscode-disabledForeground);
flex-grow: 0;
font-size: small;
font-weight: normal;
margin-left: 0.3rem;
white-space: nowrap;
}
}

.reply {
header {
background-color: rgba(0, 0, 0, 0.25);
font-size: 0.7rem;
padding: 0.2rem 0.3rem;
}

.body {
background-color: rgba(0, 0, 0, 0.25);
font-size: 0.7rem;
font-style: italic;
line-height: 0.9rem;
padding: 0.2rem 0.5rem 0.3rem 0.5rem;
word-break: break-word;
}
}

.body {
background-color: var(--vscode-sideBar-border);
color: var(--vscode-sideBar-foreground);
line-height: 1.1rem;
padding: 0.5rem;
word-break: break-word;

&.cursive {
font-style: italic;
}

&.cursive img {
transform: skew(-15deg);
}

img {
margin: -0.5rem 0;
position: relative;
vertical-align: middle;

&.gigantic {
display: block;
margin: 0 0 -0.5rem 0;
}

&.flipx {
transform: scaleX(-1);
}

&.flipy {
transform: scaleY(-1);
}

&.wide {
height: 28px;
width: 114px;
}

&.rotate {
transform: rotate(90deg);
}

&.rotateLeft {
transform: rotate(-90deg);
}

&.zeroSpace {
margin-left: -0.3rem;
}

&.cursed {
filter: brightness(0.75) contrast(2.5) grayscale(1);
}

&.party {
animation: partying 1.5s linear infinite;
}

&.shake {
animation: shaking 0.5s step-start infinite;
}

&.shake.party {
animation: partying 1.5s linear infinite,
shaking 0.5s step-start infinite;
}
}

figure {
display: inline-block;

.zeroWidth {
margin-left: -32px;
}
}

figure.big {
.zeroWidth {
margin-left: -128px;
}
}

.mention {
color: var(--vscode-sideBar-background);
background-color: var(--vscode-sideBar-foreground);
font-weight: bold;
padding: 2px;
border-radius: 2px;
}
}
}

@keyframes partying {
0% {
filter: sepia(0.5) hue-rotate(0deg) saturate(2.5);
}
to {
filter: sepia(0.5) hue-rotate(1turn) saturate(2.5);
}
}

@keyframes shaking {
0% {
translate: 0 1px;
}
10% {
translate: 2px 0;
}
20% {
translate: 1px -2px;
}
30% {
translate: -2px 1px;
}
40% {
translate: 0 -1px;
}
50% {
translate: 2px 2px;
}
60% {
translate: -1px -1px;
}
70% {
translate: -2px 2px;
}
80% {
translate: 2px 1px;
}
90% {
translate: -1px -2px;
}
to {
translate: 1px 0;
}
}
Loading

0 comments on commit 096d4ae

Please sign in to comment.