not so initial commit
This commit is contained in:
18
src/components/App.tsx
Normal file
18
src/components/App.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { RenderMessages } from "@components/Message";
|
||||
import { useState, type ReactElement } from "react";
|
||||
import { Chatbar } from "@components/Chatbar.tsx";
|
||||
import { ChannelBar } from "@components/ChannelBar.tsx";
|
||||
import { ServerBar } from "@components/ServerBar.tsx";
|
||||
|
||||
export function App(): ReactElement {
|
||||
const [serverName, setServerName] = useState("undefined");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ServerBar setServerName={setServerName} currentServer={serverName} />
|
||||
<ChannelBar servername={serverName} />
|
||||
<RenderMessages />
|
||||
<Chatbar />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
21
src/components/ChannelBar.tsx
Normal file
21
src/components/ChannelBar.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Mentions, Pill } from "@components/PillMentions";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
export function ChannelBar({servername}: {servername: string}): ReactElement {
|
||||
return (
|
||||
<div className="channel-bar">
|
||||
<h2>{servername}</h2>
|
||||
<Channel channelName="meow" mentions={0} unread={true} muted={false} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Channel({channelName, mentions, unread, muted}: {channelName: string, mentions: number, unread: boolean, muted: boolean}): ReactElement {
|
||||
return (
|
||||
<div className="channel">
|
||||
<Pill unread={unread} muted={muted} open={false} hover={false} />
|
||||
<span className="channelname">{channelName}</span>
|
||||
<Mentions count={mentions} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
67
src/components/Chatbar.tsx
Normal file
67
src/components/Chatbar.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { user } from "@localtypes";
|
||||
import { sendChatMessage, token, self, getUsers, subscribeUsers } from "@ws";
|
||||
import { useRef, useState, useSyncExternalStore, type ReactElement } from "react";
|
||||
|
||||
export function Chatbar(): ReactElement {
|
||||
const [message, setMessage] = useState("");
|
||||
const users: user[] = useSyncExternalStore(
|
||||
subscribeUsers,
|
||||
getUsers
|
||||
);
|
||||
|
||||
const editorRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
if (message.trim() !== "") {
|
||||
const msg = replaceMentions(message.trim());
|
||||
sendChatMessage({id: Date.now(), author: self, content: msg, timestamp: Date.now(), token: token!, hasSent: false});
|
||||
console.log("Sent message:", msg);
|
||||
setMessage("");
|
||||
if (editorRef.current) {
|
||||
editorRef.current.innerText = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
function replaceMentions(
|
||||
text: string,
|
||||
): string {
|
||||
// Build a quick lookup map: username -> id
|
||||
const userMap = new Map(
|
||||
users.map(u => [u.username, u.id])
|
||||
);
|
||||
|
||||
console.log(userMap);
|
||||
|
||||
// Match @[username] (username = anything except ])
|
||||
return text.replace(/@(\S+)/g, (match, username) => {
|
||||
const userId = userMap.get(username);
|
||||
console.log(userId);
|
||||
return userId ? `<@${userId}>` : match;
|
||||
});
|
||||
}
|
||||
|
||||
function handleChange(event: React.FormEvent<HTMLDivElement>) {
|
||||
if (editorRef.current) {
|
||||
setMessage(editorRef.current.innerText.trim());
|
||||
if (editorRef.current.innerText === "\n") {
|
||||
editorRef.current.innerHTML = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="textbox">
|
||||
<div
|
||||
ref={editorRef}
|
||||
className="editor"
|
||||
contentEditable
|
||||
onKeyDown={handleKeyDown}
|
||||
onInput={handleChange}
|
||||
data-text="Type here..."
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
150
src/components/Message.tsx
Normal file
150
src/components/Message.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { RefObject, useEffect, useRef, useSyncExternalStore, type ReactElement } from "react";
|
||||
import { user, message } from "@localtypes";
|
||||
import { subscribeUsers, getUsers, subscribeMessages, getMessages, self } from "@ws";
|
||||
|
||||
function formatUnixTimestamp(ts: number) {
|
||||
const date = new Date(ts);
|
||||
const now = new Date();
|
||||
|
||||
const isToday =
|
||||
date.getDate() === now.getDate() &&
|
||||
date.getMonth() === now.getMonth() &&
|
||||
date.getFullYear() === now.getFullYear();
|
||||
const HH = String(date.getHours()).padStart(2, '0');
|
||||
const min = String(date.getMinutes()).padStart(2, '0');
|
||||
if (isToday) {
|
||||
return `Today at ${HH}:${min}`;
|
||||
}
|
||||
const dd = String(date.getDate()).padStart(2, '0');
|
||||
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const yy = String(date.getFullYear())
|
||||
return `${dd}/${mm}/${yy} ${HH}:${min}`;
|
||||
}
|
||||
|
||||
export function RenderMessages(): ReactElement {
|
||||
let currentAuthor: user | null = null;
|
||||
const messages: (message | null)[] = useSyncExternalStore(
|
||||
subscribeMessages,
|
||||
getMessages
|
||||
);
|
||||
|
||||
console.log(messages);
|
||||
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: "instant" });
|
||||
}, [messages.length]);
|
||||
|
||||
return (
|
||||
<div className="messages">
|
||||
{messages.map((message, index) => {
|
||||
if (message) {
|
||||
const nextMessage = messages[index + 1];
|
||||
const nextAuthor = nextMessage ? nextMessage.author : null;
|
||||
const isLastMessageFromAuthor = nextAuthor?.id !== message.author.id;
|
||||
const inline = currentAuthor?.id === message.author.id;
|
||||
if (!inline) {
|
||||
currentAuthor = message.author;
|
||||
}
|
||||
|
||||
return (
|
||||
<Message
|
||||
key={message.id}
|
||||
author={message.author}
|
||||
content={message.content}
|
||||
inline={inline}
|
||||
bottom={isLastMessageFromAuthor}
|
||||
timestamp={message.timestamp}
|
||||
hasSent={message.hasSent}
|
||||
mention={message.content.includes(`<@${self.id}>`) || message.content.includes("@everyone") || message.content.includes("@here")}
|
||||
/>
|
||||
);
|
||||
} else return ""
|
||||
})}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Message({author, content, inline = false, bottom = false, timestamp = 0, hasSent, mention}: {author: user, content: string, inline?: boolean, bottom?: boolean, timestamp?: number, hasSent: boolean, mention: boolean}): ReactElement {
|
||||
const users: user[] = useSyncExternalStore(
|
||||
subscribeUsers,
|
||||
getUsers
|
||||
);
|
||||
|
||||
function formatContent(content: string): ReactElement[] {
|
||||
const parts = content.split('\n'); // Split the content into parts by newlines
|
||||
|
||||
return parts.flatMap((part, index) => {
|
||||
const elements: ReactElement[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
// Match all mentions in the part (e.g., <@123>)
|
||||
const mentionRegex = /<@(\d+)>/g;
|
||||
let match;
|
||||
|
||||
while ((match = mentionRegex.exec(part)) !== null) {
|
||||
const [mentionTag, userId] = match;
|
||||
const textBeforeMention = part.slice(lastIndex, match.index);
|
||||
|
||||
// Add the text before the mention as plain text
|
||||
if (textBeforeMention) {
|
||||
elements.push(<span key={`${index}-text-${lastIndex}`}>{textBeforeMention}</span>);
|
||||
}
|
||||
|
||||
// Add the Mention component
|
||||
const user = users.find((user) => user.id === parseInt(userId!)); // Use parseInt for ID comparison
|
||||
elements.push(
|
||||
<Mention
|
||||
key={`${index}-mention-${userId}`}
|
||||
name={user?.name}
|
||||
username={user ? user.username : 'unknown-user'}
|
||||
/>
|
||||
);
|
||||
|
||||
lastIndex = mentionRegex.lastIndex; // Update the last index
|
||||
}
|
||||
|
||||
// Add any remaining text after the last mention (if any)
|
||||
const remainingText = part.slice(lastIndex);
|
||||
if (remainingText) {
|
||||
elements.push(<span key={`${index}-text-${lastIndex}`}>{remainingText}</span>);
|
||||
}
|
||||
|
||||
// Add a <br /> between parts if it’s not the last part
|
||||
if (index < parts.length - 1) {
|
||||
elements.push(<br key={`br-${index}`} />);
|
||||
}
|
||||
|
||||
return elements;
|
||||
});
|
||||
}
|
||||
const formattedContent = formatContent(content);
|
||||
|
||||
return (
|
||||
<div className={`message ${inline ? "inline" : ""} ${bottom ? "bottom" : ""} ${hasSent ? "" : "sending"} ${mention ? "mentionsyou" : ""}`}>
|
||||
{!inline ? <img className="messagePfp" src={`/pfps/${author.pfp}.png`} width={48} height={48}></img> : ""}
|
||||
{!inline ? <div className="messageAuthor"><span>{self.dev ? `${author.name} (${author.username}, ${author.id})` : author.name ?? author.username}</span> {author.dev ? <Badge text="DEV" /> : ""} <Timestamp ts={timestamp} /></div> : ""}
|
||||
<div className="messageContent">{formattedContent}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Badge({text, color = "#a200ff"}: {text: string, color?: string}) {
|
||||
return (
|
||||
<div className="badge" style={{["--badge-color" as any]: color ?? "var(--brand-color)"}}>{text}</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Timestamp({ts}: {ts: number}): ReactElement {
|
||||
return (
|
||||
<div className="messageTimestamp">{formatUnixTimestamp(ts)}</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Mention({name, username}: { name?: string, username: string }): ReactElement {
|
||||
return (
|
||||
<div className="mention">@{name ?? username}</div>
|
||||
)
|
||||
}
|
||||
19
src/components/PillMentions.tsx
Normal file
19
src/components/PillMentions.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ReactElement } from "react";
|
||||
|
||||
export function Pill({unread, muted, open, hover}: {unread: boolean, muted: boolean, open: boolean, hover: boolean}): ReactElement {
|
||||
return (
|
||||
<div className="pillWrapper">
|
||||
<div className={`pill${muted ? "" : open ? " open" : hover ? " hover" : unread ? " unread" : ""}`}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Mentions({count}: {count: number}): ReactElement {
|
||||
return (
|
||||
<>
|
||||
{count !== 0 ?<div className="mentions">
|
||||
<div className="count">{count}</div>
|
||||
</div> : ""}
|
||||
</>
|
||||
)
|
||||
}
|
||||
26
src/components/ServerBar.tsx
Normal file
26
src/components/ServerBar.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Pill } from "@components/PillMentions.tsx";
|
||||
import { guild } from "@localtypes";
|
||||
import { ReactElement, useState } from "react";
|
||||
|
||||
let serverId = null;
|
||||
|
||||
export function ServerBar({setServerName, currentServer}: {setServerName: (name: string) => void, currentServer: string}): ReactElement {
|
||||
|
||||
return (
|
||||
<div className="server-bar">
|
||||
<Guild guild={{channels: [], guildId: 2, members: [], name: "meow", unread: true, muted: false}} open={"meow" === currentServer} setServerName={setServerName} />
|
||||
<Guild guild={{channels: [], guildId: 2, members: [], name: "mrrow", unread: true, muted: false}} open={"mrrow" === currentServer} setServerName={setServerName} />
|
||||
<Guild guild={{channels: [], guildId: 2, members: [], name: "mrrp", unread: true, muted: false}} open={"mrrp" === currentServer} setServerName={setServerName} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Guild({guild, open, setServerName}: {guild: guild, open: boolean, setServerName: (name: string) => void}): ReactElement {
|
||||
const [hover, setHover] = useState(false);
|
||||
return (
|
||||
<div className="guild" onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onClick={() => setServerName(guild.name)}>
|
||||
<Pill unread={guild.unread} muted={guild.muted} hover={hover} open={open} />
|
||||
<div className="guildname">{guild.name}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
8
src/index.tsx
Normal file
8
src/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "@components/App";
|
||||
|
||||
import "./style.css";
|
||||
|
||||
const domNode = document.body as HTMLBodyElement;
|
||||
const root = createRoot(domNode);
|
||||
root.render(<App />);
|
||||
193
src/style.css
Normal file
193
src/style.css
Normal file
@@ -0,0 +1,193 @@
|
||||
:root {
|
||||
--brand-color: #a200ff;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.mentions {
|
||||
color: white;
|
||||
background-color: #ff1717;
|
||||
padding: 2px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.channel-bar {
|
||||
padding: 5px;
|
||||
width: 200px;
|
||||
position: absolute;
|
||||
right: calc(100% - 250px);
|
||||
}
|
||||
|
||||
.pillWrapper {
|
||||
align-items: center;
|
||||
contain: layout size;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 8px
|
||||
}
|
||||
|
||||
.pill {
|
||||
width: 8px;
|
||||
height: 0;
|
||||
background-color: white;
|
||||
border-radius: 0 4px 4px 0;
|
||||
transition: height 0.2s ease, translate 0.3s ease, opacity 0.1s ease;
|
||||
opacity: 0;
|
||||
|
||||
&.unread {
|
||||
height: 8px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.hover {
|
||||
height: 20px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.open {
|
||||
height: 40px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.channel {
|
||||
.mentions {
|
||||
position: relative;
|
||||
bottom: 27px;
|
||||
left: 85%;
|
||||
}
|
||||
.pillWrapper {
|
||||
position: relative;
|
||||
right: 2px;
|
||||
top: 9px;
|
||||
height: 17px;
|
||||
}
|
||||
.channelname {
|
||||
position: relative;
|
||||
top: -10px;
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.server-bar {
|
||||
width: 50px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.guild {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 12px;
|
||||
.guildname {
|
||||
right: -10px;
|
||||
top: -20px;
|
||||
position: relative;
|
||||
}
|
||||
.pillWrapper {
|
||||
position: relative;
|
||||
right: 2px;
|
||||
top: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.messages {
|
||||
padding: 5px;
|
||||
width: calc(100% - 250px);
|
||||
height: calc(100% - 44px);
|
||||
overflow: hidden scroll;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-left: 50px;
|
||||
margin-bottom: 2px;
|
||||
&.bottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
position: relative;
|
||||
&.sending {
|
||||
color: #777;
|
||||
}
|
||||
&.mentionsyou {
|
||||
background-color: #b87d00;
|
||||
}
|
||||
.messagePfp {
|
||||
left: -50px;
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
cursor: pointer;
|
||||
&:active {
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
.messageAuthor {
|
||||
span {
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
max-width: fit-content;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.messageTimestamp {
|
||||
font-weight: initial;
|
||||
color: #777;
|
||||
font-size: 10px;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.messageContent {
|
||||
top: -1px;
|
||||
position: relative;
|
||||
}
|
||||
.mention {
|
||||
display: inline;
|
||||
background-color: #0099ff54;
|
||||
padding: 2px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #0099ff79;
|
||||
}
|
||||
}
|
||||
.badge {
|
||||
background-color: var(--badge-color);
|
||||
max-width: fit-content;
|
||||
padding: 2px;
|
||||
border-radius: 10%;
|
||||
font-size: 8pt;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.textbox {
|
||||
padding: 3px;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 5px;
|
||||
width: calc(100% - 250px);
|
||||
background-color: #222;
|
||||
}
|
||||
.editor:empty:before {
|
||||
content: attr(data-text);
|
||||
color: grey;
|
||||
}
|
||||
7
src/types/channel.ts
Normal file
7
src/types/channel.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { id, message } from "@localtypes";
|
||||
|
||||
export interface channel {
|
||||
channelId: id;
|
||||
messages: message[];
|
||||
|
||||
}
|
||||
10
src/types/guild.ts
Normal file
10
src/types/guild.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { id, channel, user } from "@localtypes";
|
||||
|
||||
export interface guild {
|
||||
guildId: id;
|
||||
channels: channel[];
|
||||
name: string;
|
||||
members: user[];
|
||||
unread: boolean;
|
||||
muted: boolean;
|
||||
}
|
||||
5
src/types/index.ts
Normal file
5
src/types/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from "./url"
|
||||
export * from "./user"
|
||||
export * from "./message"
|
||||
export * from "./guild"
|
||||
export * from "./channel"
|
||||
13
src/types/message.ts
Normal file
13
src/types/message.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { user } from "@localtypes";
|
||||
|
||||
export type id = number;
|
||||
|
||||
export interface message {
|
||||
id: id;
|
||||
author: user;
|
||||
content: string;
|
||||
timestamp: number;
|
||||
editedTimestamp?: number
|
||||
replyTo?: id;
|
||||
hasSent: boolean;
|
||||
}
|
||||
1
src/types/url.ts
Normal file
1
src/types/url.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type url = string;
|
||||
11
src/types/user.ts
Normal file
11
src/types/user.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { id, url } from "@localtypes";
|
||||
|
||||
export interface user {
|
||||
name?: string,
|
||||
username: string,
|
||||
pfp: url,
|
||||
id: id,
|
||||
token: null,
|
||||
password: null,
|
||||
dev: boolean;
|
||||
}
|
||||
192
src/ws/index.ts
Normal file
192
src/ws/index.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { message, user } from "@localtypes";
|
||||
|
||||
let passwd = "";
|
||||
let token = localStorage.getItem("token") ?? "";
|
||||
|
||||
while (!(passwd && passwd !== "") && !token) {
|
||||
passwd = prompt("input passwd")!;
|
||||
}
|
||||
|
||||
export let ws = new WebSocket("ws://localhost/ws");
|
||||
|
||||
let messages: message[] = [];
|
||||
let self: user;
|
||||
let users: user[] = [];
|
||||
let queue: (message | null)[] = new Array(6).fill(null);
|
||||
const listenersMessages = new Set<() => void>();
|
||||
const listenersUsers = new Set<() => void>();
|
||||
let visibleMessages: (message | null)[] = [];
|
||||
let dirty = true;
|
||||
|
||||
function rebuildVisibleMessages() {
|
||||
if (!dirty) return;
|
||||
|
||||
visibleMessages = [...messages, ...queue];
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("opened websocket!");
|
||||
if (!token) {
|
||||
ws.send(JSON.stringify({
|
||||
op: OpcodesServerbound.identify,
|
||||
data: {
|
||||
password: passwd,
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
ws.send(JSON.stringify({
|
||||
op: OpcodesServerbound.identify,
|
||||
data: {
|
||||
token,
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
ws.onmessage = message => {
|
||||
const msg = message.data;
|
||||
parseMessages(msg);
|
||||
}
|
||||
|
||||
const onclose = () => {
|
||||
console.log("ws disconnected. reconnecting");
|
||||
ws = new WebSocket("ws://localhost/ws");
|
||||
ws.onopen = () => {
|
||||
console.log("opened websocket!");
|
||||
if (!token) {
|
||||
ws.send(JSON.stringify({
|
||||
op: OpcodesServerbound.identify,
|
||||
data: {
|
||||
password: passwd,
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
ws.send(JSON.stringify({
|
||||
op: OpcodesServerbound.identify,
|
||||
data: {
|
||||
token,
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
ws.onmessage = message => {
|
||||
const msg = message.data;
|
||||
parseMessages(msg);
|
||||
}
|
||||
|
||||
ws.onclose = onclose;
|
||||
}
|
||||
|
||||
ws.onclose = onclose;
|
||||
|
||||
function notifyMessages() {
|
||||
listenersMessages.forEach((l) => l());
|
||||
}
|
||||
|
||||
function notifyUsers() {
|
||||
listenersUsers.forEach((l) => l());
|
||||
}
|
||||
|
||||
enum OpcodesClientbound {
|
||||
authorizeLogin,
|
||||
initMessages,
|
||||
updateMessages,
|
||||
registerSuccess,
|
||||
error,
|
||||
users,
|
||||
keepAlive = 9,
|
||||
}
|
||||
|
||||
enum OpcodesServerbound {
|
||||
identify,
|
||||
sendMessage,
|
||||
register,
|
||||
getUsers,
|
||||
keepAlive = 9,
|
||||
}
|
||||
|
||||
function parseMessages(message: string) {
|
||||
const json: {op: number, data: any} = JSON.parse(message) as {op: number, data: any};
|
||||
console.log(json);
|
||||
switch (json.op) {
|
||||
case OpcodesClientbound.authorizeLogin:
|
||||
token = json.data.token;
|
||||
localStorage.setItem("token", token!);
|
||||
self = json.data;
|
||||
self.token = null;
|
||||
self.password = null;
|
||||
ws.send(JSON.stringify({
|
||||
op: OpcodesServerbound.getUsers,
|
||||
data: null
|
||||
}))
|
||||
break;
|
||||
case OpcodesClientbound.initMessages:
|
||||
messages.push(...json.data);
|
||||
dirty = true;
|
||||
notifyMessages();
|
||||
break;
|
||||
case OpcodesClientbound.users:
|
||||
users = json.data;
|
||||
console.log(users);
|
||||
notifyUsers();
|
||||
break;
|
||||
case OpcodesClientbound.keepAlive:
|
||||
ws.send(JSON.stringify({
|
||||
op: OpcodesServerbound.keepAlive,
|
||||
data: null,
|
||||
}))
|
||||
break;
|
||||
case OpcodesClientbound.updateMessages:
|
||||
messages.push(json.data);
|
||||
if (json.data.author.id === self.id) {
|
||||
console.log(queue);
|
||||
queue.reverse();
|
||||
queue.pop();
|
||||
queue.reverse();
|
||||
queue.push(null as never);
|
||||
console.log(queue);
|
||||
}
|
||||
dirty = true;
|
||||
notifyMessages();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function getMessages(): (message | null)[] {
|
||||
rebuildVisibleMessages();
|
||||
return visibleMessages;
|
||||
}
|
||||
export function getUsers(): user[] {
|
||||
return users;
|
||||
}
|
||||
|
||||
export function sendChatMessage(message: message & { token?: string }) {
|
||||
if (queue.filter(m => m !== null).length < 6) {
|
||||
console.log(message);
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
if (!queue[i]) {
|
||||
queue[i] = message;
|
||||
i = 6;
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
notifyMessages();
|
||||
ws.send(JSON.stringify({op: OpcodesServerbound.sendMessage, data: message}));
|
||||
}
|
||||
}
|
||||
|
||||
export function subscribeMessages(listener: () => void) {
|
||||
listenersMessages.add(listener);
|
||||
return () => listenersMessages.delete(listener);
|
||||
}
|
||||
export function subscribeUsers(listener: () => void) {
|
||||
listenersUsers.add(listener);
|
||||
return () => listenersUsers.delete(listener);
|
||||
}
|
||||
|
||||
export { token, self };
|
||||
Reference in New Issue
Block a user