Compare commits
No commits in common. "bcd09d2521a400238c01a8781a564785741bba46" and "24b5dd835ef2a361e873b4c4e28abe0d8adf28fe" have entirely different histories.
bcd09d2521
...
24b5dd835e
|
@ -14,7 +14,6 @@
|
||||||
"polished": "^4.1.3",
|
"polished": "^4.1.3",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^6.2.1",
|
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
"styled-components": "^5.3.3"
|
"styled-components": "^5.3.3"
|
||||||
}
|
}
|
||||||
|
@ -7933,14 +7932,6 @@
|
||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/history": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.7.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
@ -13288,30 +13279,6 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
|
||||||
"version": "6.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
|
|
||||||
"integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
|
|
||||||
"dependencies": {
|
|
||||||
"history": "^5.2.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-router-dom": {
|
|
||||||
"version": "6.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
|
|
||||||
"integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
|
|
||||||
"dependencies": {
|
|
||||||
"history": "^5.2.0",
|
|
||||||
"react-router": "6.2.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.8",
|
|
||||||
"react-dom": ">=16.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-scripts": {
|
"node_modules/react-scripts": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
|
||||||
|
@ -21911,14 +21878,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||||
},
|
},
|
||||||
"history": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.7.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hoist-non-react-statics": {
|
"hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
@ -25641,23 +25600,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||||
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
|
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
|
||||||
},
|
},
|
||||||
"react-router": {
|
|
||||||
"version": "6.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
|
|
||||||
"integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
|
|
||||||
"requires": {
|
|
||||||
"history": "^5.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-router-dom": {
|
|
||||||
"version": "6.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
|
|
||||||
"integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
|
|
||||||
"requires": {
|
|
||||||
"history": "^5.2.0",
|
|
||||||
"react-router": "6.2.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-scripts": {
|
"react-scripts": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
"polished": "^4.1.3",
|
"polished": "^4.1.3",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^6.2.1",
|
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
"styled-components": "^5.3.3"
|
"styled-components": "^5.3.3"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,34 +1,14 @@
|
||||||
import { useEffect, useState, useRef } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { useQuery } from '../../hooks';
|
|
||||||
|
|
||||||
import ChatLog from '../chat-log';
|
import ChatLog from '../chat-log';
|
||||||
|
|
||||||
const maxsize = 25;
|
const maxsize = 25;
|
||||||
|
|
||||||
const retryLoop = async (func) => {
|
|
||||||
try {
|
|
||||||
return await func();
|
|
||||||
} catch (error) {
|
|
||||||
retryLoop(func);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [messages, setMessages] = useState(Array(maxsize).fill(null));
|
const [messages, setMessages] = useState(Array(maxsize).fill(null));
|
||||||
const query = useQuery();
|
|
||||||
|
|
||||||
const socket = useRef(null);
|
const socket = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const websocketAddress = query.get('ws') ?? 'ws://127.0.0.1:8080';
|
|
||||||
const attemptConnect = async () => {
|
|
||||||
const ws = new WebSocket(websocketAddress);
|
|
||||||
ws.addEventListener('message', onEvent);
|
|
||||||
ws.addEventListener('error', onDiscconect);
|
|
||||||
ws.addEventListener('close', onDiscconect);
|
|
||||||
socket.current = ws
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEvent = function (event) {
|
const onEvent = function (event) {
|
||||||
setMessages((state) => {
|
setMessages((state) => {
|
||||||
const newState = [...state];
|
const newState = [...state];
|
||||||
|
@ -37,19 +17,20 @@ function App() {
|
||||||
return newState;
|
return newState;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const onDiscconect = function (event) {
|
|
||||||
retryLoop(attemptConnect);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDiscconect();
|
const attemptConnect = () => {
|
||||||
|
socket.current = new WebSocket('ws://127.0.0.1:13337');
|
||||||
|
const onError = () => setTimeout(attemptConnect, 1000);
|
||||||
|
socket.current.addEventListener('error', onError);
|
||||||
|
socket.current.addEventListener('message', onEvent);
|
||||||
|
};
|
||||||
|
attemptConnect();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.current.removeEventListener('message', onEvent);
|
socket.current.removeEventListener('message', onEvent);
|
||||||
socket.current.removeEventListener('error', onDiscconect);
|
|
||||||
socket.current.removeEventListener('close', onDiscconect);
|
|
||||||
socket.current.close();
|
socket.current.close();
|
||||||
}
|
}
|
||||||
}, [socket, maxsize, setMessages, query]);
|
}, [socket, maxsize, setMessages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatLog log={messages} />
|
<ChatLog log={messages} />
|
||||||
|
|
|
@ -9,8 +9,6 @@ const elementMap = {
|
||||||
null: Spacer,
|
null: Spacer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const selfChats = ['self', 'birbspace'];
|
|
||||||
|
|
||||||
function ChatLog({
|
function ChatLog({
|
||||||
log,
|
log,
|
||||||
}) {
|
}) {
|
||||||
|
@ -21,9 +19,8 @@ function ChatLog({
|
||||||
}
|
}
|
||||||
const bestRenderableInterface = event.type.find((interfaceName) => Object.keys(elementMap).includes(interfaceName));
|
const bestRenderableInterface = event.type.find((interfaceName) => Object.keys(elementMap).includes(interfaceName));
|
||||||
const renderer = elementMap[bestRenderableInterface];
|
const renderer = elementMap[bestRenderableInterface];
|
||||||
const deemphasize = !selfChats.includes(event.data.via ?? 'unknown');
|
return [event, renderer];
|
||||||
return [event, renderer, deemphasize];
|
}).filter(([event, renderer]) => !!renderer);
|
||||||
}).filter(([event, renderer, deemphasize]) => !!renderer);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listRef.current.scrollTop = -1;
|
listRef.current.scrollTop = -1;
|
||||||
|
@ -31,8 +28,8 @@ function ChatLog({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List ref={listRef}>
|
<List ref={listRef}>
|
||||||
{renderableEvents.map(([event, Element, deemphasize]) => (
|
{renderableEvents.map(([event, Element]) => (
|
||||||
<Element key={event.data.id} deemphasize={deemphasize} {...event.data} />
|
<Element key={event.data.id} {...event.data} />
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,9 +19,6 @@ export const List = styled.div`
|
||||||
width: 0px;
|
width: 0px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
font-family: 'Source Code Pro', monospace;
|
|
||||||
font-size: 24px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Spacer = styled.div`
|
export const Spacer = styled.div`
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { useMemo } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { XTERM_COLORS } from '../../../index.style';
|
import { XTERM_COLORS } from '../../../index.style';
|
||||||
|
|
||||||
import { Wrapper, Author, Body, Emote, Attachments } from './message.style';
|
import { Wrapper, Author, Body, Emote } from './message.style';
|
||||||
|
|
||||||
const stringHashcode = (str) => {
|
const stringHashcode = (str) => {
|
||||||
var hash = 0, i, chr;
|
var hash = 0, i, chr;
|
||||||
|
@ -18,49 +17,27 @@ const stringHashcode = (str) => {
|
||||||
|
|
||||||
const emoteRegex = /:([^:\s]+):/g;
|
const emoteRegex = /:([^:\s]+):/g;
|
||||||
|
|
||||||
const elForType = {
|
|
||||||
image: (src) => <img src={src} />,
|
|
||||||
audio: (src) => <audio src={src} />,
|
|
||||||
video: (src) => <video src={src} />,
|
|
||||||
};
|
|
||||||
|
|
||||||
function Message({
|
function Message({
|
||||||
user_id, user_name, user_type, user_color, text, emotes, attachments,
|
user_id, user_name, user_type, user_color, text, emotes,
|
||||||
deemphasize,
|
deemphasize,
|
||||||
}) {
|
}) {
|
||||||
const fallbackColor = XTERM_COLORS[(stringHashcode(user_id.toString()) % 144) + 88];
|
const fallbackColor = XTERM_COLORS[(stringHashcode(user_id.toString()) % 144) + 88];
|
||||||
const displayFiles = useMemo(() => {
|
|
||||||
if (attachments) {
|
|
||||||
const filtered = attachments
|
|
||||||
.map(([type, url]) => [type.split('/')[0], url])
|
|
||||||
.filter(([type, url]) => Object.keys(elForType).includes(type));
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [attachments]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper type={user_type} color={user_color ?? fallbackColor} deemphasize={deemphasize}>
|
<Wrapper type={user_type} color={user_color ?? fallbackColor} deemphasize={deemphasize}>
|
||||||
<Author>
|
<Author>
|
||||||
[ {user_name} ]
|
[ {user_name} ]
|
||||||
</Author>
|
</Author>
|
||||||
{!!text && (
|
|
||||||
<Body>
|
<Body>
|
||||||
{text.split(emoteRegex).map((textOrEmote, index) => {
|
{text.split(emoteRegex).map((textOrEmote, index) => {
|
||||||
if (textOrEmote === "") return null;
|
if (textOrEmote === "") return null;
|
||||||
if (index % 2 === 0) {
|
if (index % 2 === 0) {
|
||||||
return <span>{textOrEmote}</span>;
|
return <span>{textOrEmote}</span>;
|
||||||
} else {
|
} else {
|
||||||
return <Emote src={emotes?.[textOrEmote]} title={textOrEmote} />;
|
return <Emote src={emotes?.[textOrEmote]} />;
|
||||||
}
|
}
|
||||||
}).filter((item) => item !== null)}
|
}).filter((item) => item !== null)}
|
||||||
</Body>
|
</Body>
|
||||||
)}
|
|
||||||
{!!displayFiles && (
|
|
||||||
<Attachments>
|
|
||||||
{displayFiles.map(([type, url]) => elForType[type](url))}
|
|
||||||
</Attachments>
|
|
||||||
)}
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -70,20 +47,17 @@ Message.propTypes = {
|
||||||
user_name: PropTypes.node.isRequired,
|
user_name: PropTypes.node.isRequired,
|
||||||
user_type: PropTypes.string.isRequired,
|
user_type: PropTypes.string.isRequired,
|
||||||
user_color: PropTypes.string,
|
user_color: PropTypes.string,
|
||||||
text: PropTypes.node,
|
text: PropTypes.node.isRequired,
|
||||||
emotes: PropTypes.shape({
|
emotes: PropTypes.shape({
|
||||||
[PropTypes.string]: PropTypes.string,
|
[PropTypes.string]: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
deemphasize: PropTypes.bool,
|
deemphasize: PropTypes.bool,
|
||||||
attachments: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Message.defaultProps = {
|
Message.defaultProps = {
|
||||||
text: '',
|
|
||||||
user_color: undefined,
|
user_color: undefined,
|
||||||
emotes: {},
|
emotes: {},
|
||||||
deemphasize: false,
|
deemphasize: false,
|
||||||
attachments: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Message;
|
export default Message;
|
||||||
|
|
|
@ -65,6 +65,8 @@ const setColors = ({ type, color, deemphasize }) => {
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
export const Wrapper = styled.div`
|
||||||
${setColors}
|
${setColors}
|
||||||
|
font-family: 'Source Code Pro', monospace;
|
||||||
|
font-size: 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -91,19 +93,3 @@ export const Wrapper = styled.div`
|
||||||
animation: 160s fade cubic-bezier(.6,.04,.98,.34), 0.3s slide cubic-bezier(.08,.82,.17,1);
|
animation: 160s fade cubic-bezier(.6,.04,.98,.34), 0.3s slide cubic-bezier(.08,.82,.17,1);
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Attachments = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px 6px;
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 33%;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 300px;
|
|
||||||
object-fit: scale-down;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export { default as useQuery } from './useQuery';
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
export default function useQuery() {
|
|
||||||
return new URLSearchParams(useLocation().search);
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { BrowserRouter } from "react-router-dom";
|
|
||||||
import App from './components/app/app';
|
import App from './components/app/app';
|
||||||
|
|
||||||
import { GlobalStyle } from './index.style';
|
import { GlobalStyle } from './index.style';
|
||||||
|
@ -8,9 +7,7 @@ import { GlobalStyle } from './index.style';
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<BrowserRouter>
|
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue