Skip to content

Commit 38a893e

Browse files
committed
chat working with preact
1 parent f188e4c commit 38a893e

File tree

7 files changed

+552
-253
lines changed

7 files changed

+552
-253
lines changed

frontend/overlay/components/Chat.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { h } from 'preact';
2+
import { useTwitchChat } from '@socket-studio/preact';
3+
import rehype from 'rehype';
4+
import sanitize from 'rehype-sanitize';
5+
6+
function getUsernameColor(roles: string[]) {
7+
if (roles.includes('BROADCASTER')) {
8+
return 'bg-purple-500 text-white rounded p-1';
9+
}
10+
11+
if (roles.includes('MODERATOR')) {
12+
return 'bg-purple-500 text-purple-50 rounded p-1';
13+
}
14+
15+
if (roles.includes('SUBSCRIBER')) {
16+
return 'bg-purple-500 text-purple-50 rounded p-1';
17+
}
18+
19+
return 'p-1';
20+
}
21+
22+
export function Chat() {
23+
const { chat } = useTwitchChat('codingcatdev');
24+
25+
return (
26+
<div className="text-purple-50 overflow-hidden relative">
27+
<ul className="absolute bottom-0 left-0 list-none m-0 pt-0 pr-5 pb-2 pl-2">
28+
{chat.map((message: any) => {
29+
if (!message.html) {
30+
return;
31+
}
32+
33+
const text = rehype()
34+
.data('settings', { fragment: true })
35+
.use(sanitize, {
36+
strip: ['script'],
37+
protocols: {
38+
src: ['https']
39+
},
40+
tagNames: ['img', 'marquee'],
41+
attributes: {
42+
img: ['src'],
43+
'*': ['alt']
44+
}
45+
})
46+
.processSync(message.html)
47+
.toString();
48+
49+
if (!text.length) {
50+
return;
51+
}
52+
53+
return (
54+
<li
55+
key={`${message.time}:${message.author.username}`}
56+
className="chat-message p-[1px] grid text-xs gap-1 grid-cols-[100px_1fr]"
57+
>
58+
<strong className={`${getUsernameColor(message.author.roles)} text-right truncate`}>
59+
{message.author.username}:
60+
</strong>
61+
<span className="p-1 break-words" dangerouslySetInnerHTML={{ __html: text }} />
62+
</li>
63+
);
64+
})}
65+
</ul>
66+
</div>
67+
);
68+
}

frontend/overlay/components/Home.tsx

Lines changed: 9 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,25 @@
1-
import {
2-
ApolloClient,
3-
ApolloProvider,
4-
gql,
5-
HttpLink,
6-
InMemoryCache,
7-
split,
8-
useSubscription
9-
} from '@apollo/client';
10-
import AJPrimary from '../components/AJPrimary';
11-
import { SubscriptionClient } from 'subscriptions-transport-ws';
12-
import { WebSocketLink } from '@apollo/client/link/ws';
13-
import { getMainDefinition } from '@apollo/client/utilities';
14-
import { useEffect, useRef, useState } from 'react';
15-
16-
const httpLink = new HttpLink({
17-
uri: 'https://codingcat-twitch.onrender.com/graphql'
18-
});
19-
20-
const wsLink = new WebSocketLink(
21-
new SubscriptionClient('wss:codingcat-twitch.onrender.com/graphql')
22-
);
23-
24-
// The split function takes three parameters:
25-
//
26-
// * A function that's called for each operation to execute
27-
// * The Link to use for an operation if the function returns a "truthy" value
28-
// * The Link to use for an operation if the function returns a "falsy" value
29-
const splitLink = split(
30-
({ query }) => {
31-
const definition = getMainDefinition(query);
32-
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
33-
},
34-
wsLink,
35-
httpLink
36-
);
37-
38-
const client = new ApolloClient({
39-
link: splitLink,
40-
cache: new InMemoryCache()
41-
});
42-
43-
const MESSAGES_SUBSCRIPTION = gql`
44-
subscription getMessages($username: String!) {
45-
message(channel: $username) {
46-
id
47-
message
48-
author {
49-
username
50-
roles
51-
}
52-
emotes {
53-
id
54-
name
55-
locations
56-
images {
57-
small
58-
medium
59-
large
60-
}
61-
}
62-
time
63-
}
64-
}
65-
`;
1+
import { createSocketStudioClient, SocketStudioProvider } from '@socket-studio/preact';
2+
import { useState } from 'react';
3+
import AJPrimary from './AJPrimary';
4+
import { Chat } from './Chat';
665

676
export default function Home() {
68-
const { data, loading } = useSubscription(MESSAGES_SUBSCRIPTION, {
69-
client,
70-
variables: { username: 'codingcatdev' }
71-
});
72-
const bottomRef = useRef<HTMLDivElement>(null);
73-
const [chat, setChat] = useState<any>([]);
74-
75-
useEffect(() => {
76-
if (data && data?.message?.id) {
77-
if (!chat.includes(data?.message?.id)) {
78-
setChat((c: any) => [...c, data.message]);
79-
}
80-
}
81-
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
82-
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
83-
}, [data]);
7+
const client = createSocketStudioClient('https://codingcat-twitch.onrender.com/graphql');
8+
const [isBrowser] = useState(typeof window !== 'undefined');
849

8510
return (
86-
<ApolloProvider client={client}>
11+
<SocketStudioProvider client={client}>
8712
<main className="grid grid-rows-[1fr_8px_144px] h-screen overflow-hidden">
8813
<div className="overlay-top h-full w-full" />
8914
<div className="w-full bg-purple-900 h-2" />
9015
<div className="grid grid-cols-[400px_minmax(500px,_1fr)_140px] w-full bg-gradient-to-r to-purple-700 via-purple-500 from-pink-500">
91-
<div className="flex flex-grow">
92-
<div className="messages flex flex-col overflow-y-scroll max-w-xl p-1 relative">
93-
{loading ? (
94-
<div className="rounded-xl p-1 bg-purple-500 text-purple-50 px-1">
95-
No Messages Yet...
96-
</div>
97-
) : (
98-
<>
99-
{chat.map((c: any) => (
100-
<div key={c.id} className="flex gap-1">
101-
<div className="rounded bg-purple-500 text-purple-50 px-1">
102-
{c?.author?.username}
103-
</div>
104-
<div className="text-purple-50">{c.message}</div>
105-
</div>
106-
))}
107-
<div ref={bottomRef} className="mt-6" />
108-
</>
109-
)}
110-
</div>
111-
</div>
16+
<Chat />
11217
<div></div>
11318
<div className="w-36 h-36 p-2">
11419
<AJPrimary className="block w-32 h-32" />
11520
</div>
11621
</div>
11722
</main>
118-
</ApolloProvider>
23+
</SocketStudioProvider>
11924
);
12025
}

frontend/overlay/next.config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** @type {import('next').NextConfig} */
2+
const withPreact = require('next-plugin-preact');
23
const nextConfig = {
3-
reactStrictMode: true,
4-
}
4+
reactStrictMode: true
5+
};
56

6-
module.exports = nextConfig
7+
module.exports = withPreact(nextConfig);

frontend/overlay/notypes.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module '@socket-studio/preact';

frontend/overlay/package.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12-
"@apollo/client": "^3.6.9",
13-
"graphql": "^16.5.0",
12+
"@socket-studio/preact": "1.0.0-canary.17",
1413
"next": "12.2.0",
15-
"react": "18.2.0",
16-
"react-dom": "18.2.0",
17-
"subscriptions-transport-ws": "^0.11.0",
14+
"next-plugin-preact": "^3.0.7",
15+
"preact": "^10.8.2",
16+
"preact-render-to-string": "^5.2.0",
17+
"react": "npm:@preact/compat@17.1.1",
18+
"react-dom": "npm:@preact/compat@17.1.1",
19+
"react-ssr-prepass": "npm:preact-ssr-prepass@^1.2.0",
20+
"rehype": "^11.0.0",
21+
"rehype-sanitize": "^4.0.0",
1822
"ws": "^8.8.0"
1923
},
2024
"devDependencies": {

0 commit comments

Comments
 (0)