Chatserver
This commit is contained in:
parent
75f7a7ed61
commit
3cca3b269e
9 changed files with 1001 additions and 22 deletions
123
package-lock.json
generated
123
package-lock.json
generated
|
@ -9,6 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@vercel/postgres-kysely": "^0.8.0",
|
||||
"cookies-next": "^4.1.1",
|
||||
"formik": "^2.4.5",
|
||||
"kysely": "^0.27.3",
|
||||
"nanoid": "^5.0.7",
|
||||
|
@ -18,6 +19,7 @@
|
|||
"react-markdown": "^9.0.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"use-sound": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -175,6 +177,11 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
|
||||
|
@ -183,6 +190,11 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
|
@ -423,6 +435,29 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookies-next": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookies-next/-/cookies-next-4.1.1.tgz",
|
||||
"integrity": "sha512-20QaN0iQSz87Os0BhNg9M71eM++gylT3N5szTlhq2rK6QvXn1FYGPB4eAgU4qFTunbQKhD35zfQ95ZWgzUy3Cg==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^16.10.2",
|
||||
"cookie": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookies-next/node_modules/@types/node": {
|
||||
"version": "16.18.97",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.97.tgz",
|
||||
"integrity": "sha512-4muilE1Lbfn57unR+/nT9AFjWk0MtWi5muwCEJqnOvfRQDbSfLCUdN7vCIg8TYuaANfhLOV85ve+FNpiUsbSRg=="
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
|
@ -484,6 +519,60 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
|
||||
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client/node_modules/utf-8-validate": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
|
||||
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client/node_modules/ws": {
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
|
||||
"integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
|
@ -1980,6 +2069,32 @@
|
|||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.7.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
|
||||
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io-client": "~6.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
|
@ -2280,6 +2395,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vercel/postgres-kysely": "^0.8.0",
|
||||
"cookies-next": "^4.1.1",
|
||||
"formik": "^2.4.5",
|
||||
"kysely": "^0.27.3",
|
||||
"nanoid": "^5.0.7",
|
||||
|
@ -19,6 +20,7 @@
|
|||
"react-markdown": "^9.0.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"use-sound": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
392
src/app/chat/page.tsx
Normal file
392
src/app/chat/page.tsx
Normal file
|
@ -0,0 +1,392 @@
|
|||
'use client';
|
||||
import { ChatLayout } from "@/layout/ChatLayout/ChatLayout";
|
||||
import { io } from "socket.io-client";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { getCookie } from 'cookies-next';
|
||||
import styles from "./styles.module.css";
|
||||
import Markdown from "react-markdown";
|
||||
import { Conditional } from "@/components/utility/Conditional";
|
||||
|
||||
type MessageType = {
|
||||
channel: string,
|
||||
message: string,
|
||||
me: boolean,
|
||||
user: string,
|
||||
id: string,
|
||||
timestamp: number
|
||||
};
|
||||
|
||||
type Res = {type: string, spec: string, data: any};
|
||||
|
||||
export default function Home() {
|
||||
const message_translation_tables = new Map([
|
||||
["ERR_CHAT_JOIN", new Map([
|
||||
["main", "Error when joining chat"],
|
||||
["JOINED", "You've already joined Chat"],
|
||||
["MALFORMED_TOKEN", "You have a malformed token"],
|
||||
["INVALID_TOKEN", "Your token is invalid or expired"],
|
||||
["INVALID_USER", "Your token has no user or your user is banned (shame on you)"],
|
||||
])],
|
||||
["ERR_CHAT_WHOISTHIS", new Map([
|
||||
["main", "Error when fetching user"],
|
||||
["NO_USER", "Unauthenticated request"],
|
||||
["MALFORMED_USER", "You have a malformed user ID"],
|
||||
["INVALID_USER", "No user with this ID"],
|
||||
])],
|
||||
["ERR_CHAT_CHGCHANNEL", new Map([
|
||||
["main", "Error when changing channel"],
|
||||
["NO_USER", "Unauthenticated request"],
|
||||
["MALFORMED_CHANNEL", "You have a malformed channel selection; try again"],
|
||||
["INVALID_CHANNEL", "No channel with this name"],
|
||||
["CHANNEL_UNREADABLE", "You are not authorized to read this channel"]
|
||||
])],
|
||||
["ERR_CHAT_MESSAGE", new Map([
|
||||
["main", "Error when sending message"],
|
||||
["NO_USER", "Unauthenticated request"],
|
||||
["MALFORMED_MESSAGE", "You have a malformed message; try again"],
|
||||
["DATABASE_ERROR", "The database encountered an error (contact me@abtmtr.link)"],
|
||||
["CHANNEL_UNWRITABLE", "You are not authorized to write to this channel"],
|
||||
["RATE_LIMIT", "You are being rate-limited"],
|
||||
["RATE_LIMIT_BAN", "You have been disconnected due to exceeding the rate-limit"]
|
||||
])],
|
||||
["ERR_CHAT_RMMESSAGE", new Map([
|
||||
["main", "Error when removing message"],
|
||||
["NO_USER", "Unauthenticated request"],
|
||||
["MALFORMED_MESSAGEID", "You have a malformed message selection; try again"],
|
||||
["NULL_MESSAGE", "No message with this ID"],
|
||||
["NUH_UH", "You are not authorized to remove this message"],
|
||||
["DATABASE_ERROR", "The database encountered an error (contact me@abtmtr.link)"],
|
||||
["RATE_LIMIT", "You are being rate-limited"],
|
||||
["RATE_LIMIT_BAN", "You have been disconnected due to exceeding the rate-limit"]
|
||||
])],
|
||||
["ERR_CHAT_USERS", new Map([
|
||||
["main", "Error when fetching users"],
|
||||
["NO_USER", "Unauthenticated request"]
|
||||
])],
|
||||
["ERR_CHAT_CHANNELS", new Map([
|
||||
["main", "Error when fetching channels"],
|
||||
["NO_USER", "Unauthenticated request"],
|
||||
["DATABASE_ERROR", "The database encountered an error (contact me@abtmtr.link)"]
|
||||
])],
|
||||
["ERR_CHAT_MESSAGES", new Map([
|
||||
["main", "Error when fetching messages"],
|
||||
["NO_USER", "Unauthenticated request"],
|
||||
["DATABASE_ERROR", "The database encountered an error (if this continues, contact me@abtmtr.link)"],
|
||||
["CHANNEL_UNREADABLE", "You are not authorized to read this channel"]
|
||||
])]
|
||||
]);
|
||||
const data_translation_tables:Map<string, {[key:string]:any}> = new Map([
|
||||
["RATE_LIMIT", {
|
||||
reset: (x:number) => `(Refreshes in ${x} seconds)`
|
||||
}]
|
||||
])
|
||||
const socketLocation = "https://chatserver.abtmtr.link/";
|
||||
const socketObject = useMemo(() => io(socketLocation), []);
|
||||
const socket = socketObject;
|
||||
|
||||
const [ connected, setConnected ] = useState(false);
|
||||
const [ error, _setError ] = useState("");
|
||||
const [ whoami, _setWhoAmI ] = useState<{[key:string]:any}>({});
|
||||
const whoamiRef = useRef(whoami);
|
||||
const setWhoAmI = (x:any) => {whoamiRef.current = x;_setWhoAmI(x);};
|
||||
const [ channel, setChannel ] = useState("");
|
||||
const [ channels, setChannels ] = useState<{
|
||||
name: string,
|
||||
action: () => void,
|
||||
readable: boolean,
|
||||
writable: boolean
|
||||
}[]>([]);
|
||||
const [ users, _setUsers ] = useState<{
|
||||
id: string,
|
||||
username: string,
|
||||
instance: string,
|
||||
admin: boolean
|
||||
}[]>([]);
|
||||
const usersRef = useRef(users);
|
||||
const setUsers = (x:any) => {usersRef.current = x;_setUsers(x);};
|
||||
const [ messages, _setMessages ] = useState<MessageType[]>([]);
|
||||
const messagesRef = useRef(messages);
|
||||
const setMessages = (x:any) => {messagesRef.current = x;_setMessages(x);};
|
||||
|
||||
const [ isLoading, setIsLoading ] = useState(false);
|
||||
|
||||
/* SECT: LHUA; Lord help us all these ones */
|
||||
const [ textBox, setTextBox ] = useState<React.ReactNode>(null);
|
||||
let Message = useRef<(message:MessageType) => React.ReactNode>(() => <></>);
|
||||
const [, forceUpdate] = useState({});
|
||||
const externCachedUsers = useRef(new Map());
|
||||
/* END_SECT LHUA; */
|
||||
|
||||
|
||||
const chatInput = useRef<HTMLTextAreaElement>(null);
|
||||
const chatPaneRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function unsetError() {
|
||||
_setError("");
|
||||
}
|
||||
function setError(type:string, spec:string, data?:any) {
|
||||
let errorTable = message_translation_tables.get(type);
|
||||
let dataTable = data_translation_tables.get(spec);
|
||||
|
||||
let errorTableType;
|
||||
let errorTableSpec;
|
||||
if (errorTable == null) {
|
||||
errorTableType = type;
|
||||
errorTableSpec = spec;
|
||||
} else {
|
||||
errorTableType = errorTable.get("main");
|
||||
if (errorTableType == null)
|
||||
errorTableType = type;
|
||||
errorTableSpec = errorTable.get(spec);
|
||||
if (errorTableSpec == null)
|
||||
errorTableSpec = spec;
|
||||
}
|
||||
|
||||
let dataTableSpec;
|
||||
if (dataTable != null && data != null) {
|
||||
dataTableSpec = Object.entries(data).reduce((prev, [k,v]) => prev + " " + dataTable[k](v), "");
|
||||
}
|
||||
|
||||
if (dataTableSpec == null)
|
||||
_setError(`${errorTableType}: ${errorTableSpec}`);
|
||||
else
|
||||
_setError(`${errorTableType}: ${errorTableSpec} ${dataTableSpec}`)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
/* SECT: BSN; Because "soft navigation" */
|
||||
// @ts-ignore
|
||||
if (window.socket && window.socket !== socket)
|
||||
// @ts-ignore
|
||||
window.socket.disconnect();
|
||||
// @ts-ignore
|
||||
window.socket = socket;
|
||||
/* END_SECT BSN; */
|
||||
|
||||
function onConnect() {
|
||||
setConnected(true);
|
||||
|
||||
const token = getCookie('token');
|
||||
if (token != null)
|
||||
socket.emit('SIG_CHAT_JOIN', { token }, joinChatRes);
|
||||
|
||||
setTextBox(<div className={styles.textBox}>
|
||||
<textarea rows={2} className={styles.textBoxInput} ref={chatInput}></textarea>
|
||||
<button className={styles.textBoxButton} onClick={() => socket.emit('SIG_CHAT_MESSAGE', { message: chatInput.current?.value }, messageChatRes)}>Send</button>
|
||||
</div>)
|
||||
Message.current = (message:MessageType) => {
|
||||
let user;
|
||||
user = usersRef.current.find(x => x.id == message.user);
|
||||
if (user == null)
|
||||
user = externCachedUsers.current.get(message.user);
|
||||
if (user == null) {
|
||||
new Promise((res, rej) => {
|
||||
socket.emit('SIG_CHAT_WHOISTHIS', { id: message.user }, ({ type, spec, data }:Res) => {
|
||||
if (type.includes("ERR"))
|
||||
rej(`${type} - ${spec}`);
|
||||
else {
|
||||
externCachedUsers.current.set(data.id, data);
|
||||
res("");
|
||||
forceUpdate({});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
const canDelete = whoamiRef.current.admin || whoamiRef.current.id == message.user;
|
||||
const timeSent = new Date(message.timestamp * 1000).toLocaleTimeString();
|
||||
return <div className={styles.Message} key={message.id}>
|
||||
<h3 className={styles.MessageHeader}>
|
||||
<a href={`/jams/user/${user?.id}`} target="_blank">{user?.username}@{user?.instance}</a>
|
||||
{" "}
|
||||
<Conditional condition={canDelete}
|
||||
truthy={<button onClick={() => socket.emit('SIG_CHAT_RMMESSAGE', { messageId: message.id }, rmmessageChatRes)} className={styles.deleteButton}>
|
||||
<small>{timeSent}</small>
|
||||
</button>}
|
||||
falsy={<small>{timeSent}</small>}
|
||||
/>
|
||||
</h3>
|
||||
<Markdown>{message.message}</Markdown>
|
||||
</div>
|
||||
};
|
||||
}
|
||||
function onDisconnect() {
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
if (socket.connected && !connected) {
|
||||
onConnect();
|
||||
}
|
||||
|
||||
socket.on("connect", onConnect);
|
||||
socket.on("disconnect", onDisconnect);
|
||||
|
||||
window.addEventListener("navigate", socket.disconnect);
|
||||
|
||||
return () => {
|
||||
socket.off("connect", onConnect);
|
||||
socket.off("disconnect", onDisconnect);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// user stuff
|
||||
|
||||
function addUser(userdata:{
|
||||
id: string,
|
||||
username: string,
|
||||
instance: string,
|
||||
admin: boolean
|
||||
}) {
|
||||
setUsers(usersRef.current.concat([ userdata ]));
|
||||
}
|
||||
|
||||
function removeUser(userId:string) {
|
||||
const index = usersRef.current.findIndex(x => x.id == userId);
|
||||
if (index < 0) return;
|
||||
setUsers(usersRef.current.toSpliced(index, 1));
|
||||
}
|
||||
|
||||
function usersChatRes({ type, spec, data }:Res) {
|
||||
unsetError();
|
||||
if (type.includes("ERR")) {
|
||||
setError(type, spec, data);
|
||||
} else {
|
||||
setUsers(data);
|
||||
}
|
||||
}
|
||||
|
||||
socket.off("RES_CHAT_JOIN");
|
||||
socket.on("RES_CHAT_JOIN", ({ user }) => {
|
||||
addUser(user);
|
||||
});
|
||||
|
||||
socket.off("RES_CHAT_LEAVE");
|
||||
socket.on("RES_CHAT_LEAVE", ({ userId }) => {
|
||||
removeUser(userId);
|
||||
});
|
||||
|
||||
const prevscrtop = useRef(0);
|
||||
useEffect(() => {
|
||||
if (
|
||||
chatPaneRef.current != null
|
||||
&& chatPaneRef.current.scrollTop >= prevscrtop.current
|
||||
) {
|
||||
chatPaneRef.current.scrollTop = chatPaneRef.current.scrollHeight;
|
||||
prevscrtop.current = chatPaneRef.current.scrollTop;
|
||||
}
|
||||
}, [messages])
|
||||
|
||||
// message stuff
|
||||
|
||||
function addMessage(messagesdata:MessageType) {
|
||||
setMessages(messagesRef.current.concat([ messagesdata ]));
|
||||
}
|
||||
|
||||
function removeMessage(messageId:string) {
|
||||
const index = messagesRef.current.findIndex(x => x.id == messageId);
|
||||
if (index < 0) return;
|
||||
setMessages(messagesRef.current.toSpliced(index, 1));
|
||||
}
|
||||
function messagesChatRes({ type, spec, data }:Res) {
|
||||
unsetError();
|
||||
if (type.includes("ERR")) {
|
||||
setError(type, spec, data);
|
||||
} else {
|
||||
setMessages(data);
|
||||
}
|
||||
}
|
||||
|
||||
function messageChatRes({ type, spec, data }:Res) {
|
||||
unsetError();
|
||||
if (type.includes("ERR")) {
|
||||
setError(type, spec, data);
|
||||
} else {
|
||||
addMessage(data);
|
||||
}
|
||||
if (chatInput.current != null)
|
||||
chatInput.current.value = "";
|
||||
}
|
||||
|
||||
function rmmessageChatRes({ type, spec, data }:Res) {
|
||||
unsetError();
|
||||
if (type.includes("ERR")) {
|
||||
setError(type, spec, data);
|
||||
} else {
|
||||
removeMessage(data.messageId);
|
||||
}
|
||||
}
|
||||
|
||||
socket.off("RES_CHAT_MESSAGE");
|
||||
socket.on("RES_CHAT_MESSAGE", ({ message }) => {
|
||||
console.log(messages);
|
||||
addMessage(message);
|
||||
});
|
||||
|
||||
socket.off("RES_CHAT_RMMESSAGE");
|
||||
socket.on("RES_CHAT_RMMESSAGE", ({ messageId }) => {
|
||||
removeMessage(messageId);
|
||||
});
|
||||
|
||||
function joinChatRes({ type, spec, data }:Res) {
|
||||
unsetError();
|
||||
if (type.includes("ERR")) {
|
||||
setError(type, spec, data);
|
||||
} else {
|
||||
setWhoAmI(data);
|
||||
socket.emit('SIG_CHAT_CHANNELS', {}, channelsChatRes);
|
||||
}
|
||||
}
|
||||
|
||||
function channelsChatRes({ type, spec, data }:Res) {
|
||||
unsetError();
|
||||
if (type.includes("ERR")) {
|
||||
setError(type, spec, data);
|
||||
} else {
|
||||
setChannels(data.map((x:{[key:string]:any}) => {
|
||||
x.action = () => {
|
||||
socket.emit('SIG_CHAT_CHGCHANNEL', { channel: x.name }, chgchannelChatRes);
|
||||
}
|
||||
return x;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function chgchannelChatRes({ type, spec, data }:Res) {
|
||||
unsetError();
|
||||
if (type.includes("ERR")) {
|
||||
setError(type, spec, data);
|
||||
} else {
|
||||
prevscrtop.current = 0;
|
||||
setChannel(data.channel);
|
||||
socket.emit('SIG_CHAT_USERS', {}, usersChatRes);
|
||||
socket.emit('SIG_CHAT_MESSAGES', {}, messagesChatRes);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatLayout
|
||||
title="Chat"
|
||||
subtitle={error}
|
||||
props={{
|
||||
channels,
|
||||
users,
|
||||
currentChannel: channel,
|
||||
connected,
|
||||
// @ts-ignore
|
||||
whoami,
|
||||
chatPaneRef
|
||||
}}
|
||||
footerChildren={textBox}
|
||||
>
|
||||
{messages.length > 0 && !isLoading ?
|
||||
messages.map(Message.current)
|
||||
: (whoami.admin || channels.find(x => x.name == channel)?.writable ?
|
||||
<div style={{textAlign: "center"}}>
|
||||
<h1>This channel is empty.</h1>
|
||||
<p>Use the bar below to post something.</p>
|
||||
</div>
|
||||
: <></>)
|
||||
}
|
||||
</ChatLayout>
|
||||
)
|
||||
}
|
33
src/app/chat/styles.module.css
Normal file
33
src/app/chat/styles.module.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
.textBox {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.textBoxInput {
|
||||
background-color: var(--bg-2);
|
||||
border: 1px solid var(--fg-2);
|
||||
color: var(--fg-2);
|
||||
padding: 4px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.textBoxButton {
|
||||
background-color: var(--bg-2);
|
||||
border: 1px solid var(--fg-2);
|
||||
color: var(--fg-2);
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.Message {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
/* .MessageHeader {
|
||||
font-size: 1.25em;
|
||||
} */
|
||||
|
||||
.deleteButton:hover {
|
||||
color: hsl(0, 20%, 75%);
|
||||
}
|
275
src/layout/ChatLayout/ChatLayout.module.css
Normal file
275
src/layout/ChatLayout/ChatLayout.module.css
Normal file
|
@ -0,0 +1,275 @@
|
|||
@keyframes slidein {
|
||||
0% {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
99% {
|
||||
position: relative;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
position: static;
|
||||
top: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slidein_header {
|
||||
0% {
|
||||
position: relative;
|
||||
left: -10px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
99% {
|
||||
position: relative;
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
position: static;
|
||||
top: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.ChatLayout {
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: 256px 1fr;
|
||||
}
|
||||
|
||||
.ChatLayout_Aside {
|
||||
padding: 4px 16px;
|
||||
background-color: var(--bg-1);
|
||||
color: var(--fg-1);
|
||||
border-right: 1px solid var(--neutral);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.ChatLayout_Aside.Modifier_Open {}
|
||||
|
||||
.ChatLayout_Inner {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: 48px auto 64px;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header {
|
||||
padding: 6px 16px;
|
||||
background-color: var(--bg-2);
|
||||
color: var(--fg-2);
|
||||
border-bottom: 1px solid var(--neutral);
|
||||
overflow: hidden;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header>* {
|
||||
animation-duration: 0.25s;
|
||||
animation-name: slidein_header;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header.Modifier_Back {
|
||||
padding: 6px 8px;
|
||||
grid-template-columns: 32px 1fr;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header_Back_Button {
|
||||
font-family: var(--font-MaterialSymbols);
|
||||
font-size: 24px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header_Menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header_Title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
align-content: center;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header_Title h1 {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
font-size: 1em;
|
||||
text-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header_Title span {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
font-size: 0.75em;
|
||||
text-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Footer {
|
||||
padding: 6px 16px;
|
||||
background-color: var(--bg-1);
|
||||
color: var(--fg-1);
|
||||
border-top: 1px solid var(--neutral);
|
||||
overflow: hidden;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Main {
|
||||
display: inline-block;
|
||||
background-color: var(--bg-1);
|
||||
color: var(--fg-1);
|
||||
width: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Main_Content {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-2);
|
||||
color: var(--fg-2);
|
||||
padding: 0 16px;
|
||||
max-width: 50em;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
margin-bottom: -4px;
|
||||
|
||||
/* border-left: 1px solid var(--neutral);
|
||||
border-right: 1px solid var(--neutral); */
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Main_Content>* {
|
||||
animation-duration: 0.25s;
|
||||
animation-name: slidein;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Main_Content img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.ChatLayout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.ChatLayout_Aside {
|
||||
position: absolute;
|
||||
height: calc(100% - 48px);
|
||||
width: 100%;
|
||||
top: 48px;
|
||||
z-index: 50;
|
||||
left: -100%;
|
||||
transition: left 0.125s;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header_Menu {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ChatLayout_Inner_Header {
|
||||
padding: 6px 8px;
|
||||
grid-template-columns: 32px auto;
|
||||
}
|
||||
|
||||
.ChatLayout_Aside.Modifier_Open {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* sidebar */
|
||||
|
||||
.Main_Image {
|
||||
text-align: center;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.Main_Image img {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.Main_List {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.Main_List>a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.Main_List_CurrentLink>div {
|
||||
background-color: var(--bg-2);
|
||||
}
|
||||
|
||||
.Main_List_Button {
|
||||
color: var(--accent-color);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.Main_List_Button_Badge {
|
||||
background-color: rgb(255, 64, 64);
|
||||
color: white;
|
||||
padding: 0 4px;
|
||||
margin: 4px;
|
||||
float: right;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.Main_List_CurrentLink .Main_List_Button_Badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Main_List_Button_Slim {
|
||||
padding: 2px 8px !important;
|
||||
background-color: var(--bg-3);
|
||||
color: var(--fg-2);
|
||||
}
|
||||
|
||||
.Main_List_Button_Slim:hover {
|
||||
background-color: var(--bg-2);
|
||||
color: var(--fg-1);
|
||||
}
|
||||
|
||||
/* .Main_List_Button_Slim:active {
|
||||
scale: 1;
|
||||
} */
|
140
src/layout/ChatLayout/ChatLayout.tsx
Normal file
140
src/layout/ChatLayout/ChatLayout.tsx
Normal file
|
@ -0,0 +1,140 @@
|
|||
'use client';
|
||||
|
||||
import { ConditionalNull } from "@/components/utility/Conditional";
|
||||
import React, { useState } from "react";
|
||||
import styles from "./ChatLayout.module.css";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
export function ChatLayout<PropFormat>({
|
||||
title,
|
||||
subtitle,
|
||||
backButton = false,
|
||||
children,
|
||||
footerChildren,
|
||||
props,
|
||||
}:{
|
||||
title: string,
|
||||
subtitle?: string,
|
||||
backButton?: boolean,
|
||||
children: React.ReactNode,
|
||||
footerChildren: React.ReactNode,
|
||||
props?:{
|
||||
channels: {
|
||||
name:string,
|
||||
action: () => void,
|
||||
readable: boolean,
|
||||
writable: boolean
|
||||
}[],
|
||||
users: {
|
||||
id: string,
|
||||
username: string,
|
||||
instance: string,
|
||||
admin: boolean
|
||||
}[],
|
||||
currentChannel: string,
|
||||
connected: boolean,
|
||||
whoami?: {
|
||||
id: string,
|
||||
username: string,
|
||||
instance: string
|
||||
},
|
||||
chatPaneRef: React.Ref<HTMLDivElement>
|
||||
}
|
||||
}) {
|
||||
const [menuOpen, setMenuOpen] = useState<boolean>(false);
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className={`${styles.ChatLayout}`}>
|
||||
<aside className={`${styles.ChatLayout_Aside} ${menuOpen ? styles.Modifier_Open : ""}`}>
|
||||
<h1 style={{marginBottom:0}}>Chat</h1>
|
||||
<p>
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
< abtmtr.link
|
||||
</button>
|
||||
</p>
|
||||
<hr />
|
||||
<ConditionalNull condition={props?.connected == true && props?.channels != null}>
|
||||
<p>Channels</p>
|
||||
<div className={styles.Main_List}>
|
||||
{props?.channels?.map((channel) => (<button
|
||||
onClick={channel.action}
|
||||
className={props.currentChannel === channel.name ? styles.Main_List_CurrentLink : ""}
|
||||
title={`#${channel.name}`}
|
||||
>
|
||||
<div className={`fw ${styles.Main_List_Button}`}>
|
||||
<span className="icon">tag</span>
|
||||
<span>{channel.name}</span>
|
||||
</div>
|
||||
</button>))}
|
||||
</div>
|
||||
</ConditionalNull>
|
||||
<ConditionalNull condition={
|
||||
props?.connected == true && props?.channels != null
|
||||
&& props?.connected == true && props?.users != null
|
||||
}>
|
||||
<hr />
|
||||
</ConditionalNull>
|
||||
<ConditionalNull condition={props?.connected == true && props?.users != null}>
|
||||
<p>Users</p>
|
||||
<div className={styles.Main_List}>
|
||||
{props?.users.map((user) => (<Link
|
||||
href={`/jams/user/${user.id}`}
|
||||
className={props.whoami?.id === user.id ? styles.Main_List_CurrentLink : ""}
|
||||
title={`${user.username}@${user.instance}`}
|
||||
>
|
||||
<div className={`fw ${styles.Main_List_Button}`}>
|
||||
<span className="icon">person</span>
|
||||
<span>{user.username}@{user.instance}</span>
|
||||
</div>
|
||||
</Link>))}
|
||||
</div>
|
||||
</ConditionalNull>
|
||||
<ConditionalNull condition={
|
||||
props?.connected == true && props?.users != null
|
||||
}>
|
||||
<hr />
|
||||
</ConditionalNull>
|
||||
<p>Chat API v2</p>
|
||||
<p><i>made with hate for React<br />and love for irisnk.me</i></p>
|
||||
</aside>
|
||||
<div className={styles.ChatLayout_Inner}>
|
||||
<div className={`${
|
||||
styles.ChatLayout_Inner_Header
|
||||
} ${
|
||||
backButton ? styles.Modifier_Back : ""
|
||||
}`}>
|
||||
<ConditionalNull condition={!backButton}>
|
||||
<div className={`${styles.ChatLayout_Inner_Header_Back} ${styles.ChatLayout_Inner_Header_Menu}`}>
|
||||
<button className={styles.ChatLayout_Inner_Header_Back_Button} onClick={() => setMenuOpen(!menuOpen)}>
|
||||
menu
|
||||
</button>
|
||||
</div>
|
||||
</ConditionalNull>
|
||||
<ConditionalNull condition={backButton}>
|
||||
<div className={styles.ChatLayout_Inner_Header_Back}>
|
||||
<button className={styles.ChatLayout_Inner_Header_Back_Button} onClick={() => router.back()}>
|
||||
arrow_back
|
||||
</button>
|
||||
</div>
|
||||
</ConditionalNull>
|
||||
<div className={styles.ChatLayout_Inner_Header_Title}>
|
||||
<h1>{title}</h1>
|
||||
<span>{subtitle}</span>
|
||||
</div>
|
||||
</div>
|
||||
<main className={styles.ChatLayout_Inner_Main} ref={props?.chatPaneRef}>
|
||||
<div className={`${styles.ChatLayout_Inner_Main_Content} MainContent`}>
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
<div className={`${styles.ChatLayout_Inner_Footer}`}>
|
||||
{footerChildren}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -5,18 +5,22 @@ import { useState } from "react";
|
|||
import styles from "./MainLayout.module.css";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export function Desktop({
|
||||
export function Desktop<PropFormat>({
|
||||
title,
|
||||
subtitle,
|
||||
currentPage,
|
||||
backButton = false,
|
||||
children,
|
||||
props,
|
||||
sidebar
|
||||
}:{
|
||||
title: string,
|
||||
subtitle?: string,
|
||||
currentPage: string,
|
||||
backButton?: boolean,
|
||||
children: React.ReactNode,
|
||||
sidebar: React.ReactNode
|
||||
props?:PropFormat,
|
||||
sidebar: (x:{currentPage:string, props?:PropFormat}) => React.ReactNode
|
||||
}) {
|
||||
const [menuOpen, setMenuOpen] = useState<boolean>(false);
|
||||
const router = useRouter();
|
||||
|
@ -24,7 +28,7 @@ export function Desktop({
|
|||
return (
|
||||
<div className={`${styles.MainLayout}`}>
|
||||
<aside className={`${styles.MainLayout_Aside} ${menuOpen ? styles.Modifier_Open : ""}`}>
|
||||
{sidebar}
|
||||
{sidebar({ currentPage, props })}
|
||||
</aside>
|
||||
<div className={styles.MainLayout_Inner}>
|
||||
<div className={`${
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { SidebarMain } from "./Sidebar/Main/Main";
|
||||
import { Desktop } from "./Desktop";
|
||||
|
||||
export function MainLayout({
|
||||
export function MainLayout<PropFormat>({
|
||||
title,
|
||||
subtitle,
|
||||
backButton = false,
|
||||
children,
|
||||
props,
|
||||
sidebar,
|
||||
currentPage
|
||||
}:{
|
||||
|
@ -13,8 +14,9 @@ export function MainLayout({
|
|||
subtitle?: string,
|
||||
backButton?: boolean,
|
||||
children: React.ReactNode,
|
||||
sidebar?: React.ReactNode,
|
||||
props?:PropFormat,
|
||||
sidebar?: (x:{ currentPage:string, props?:PropFormat }) => React.ReactNode,
|
||||
currentPage: string
|
||||
}) {
|
||||
return <Desktop title={title} subtitle={subtitle} backButton={backButton} sidebar={sidebar ?? <SidebarMain currentPage={currentPage} />}>{children}</Desktop>
|
||||
return <Desktop title={title} subtitle={subtitle} currentPage={currentPage} props={props} backButton={backButton} sidebar={sidebar ?? SidebarMain}>{children}</Desktop>
|
||||
}
|
|
@ -3,7 +3,7 @@ import Link from "next/link";
|
|||
import styles from "./Main.module.css";
|
||||
// import useSound from 'use-sound';
|
||||
|
||||
export function SidebarMain({currentPage}:{currentPage:string}) {
|
||||
export function SidebarMain<PropFormat>({currentPage, props}:{currentPage:string, props?:PropFormat}) {
|
||||
// const [openPlay] = useSound("/sfx/s_open.mp3");
|
||||
// const [impossiblePlay] = useSound("/sfx/s_impossible.mp3");
|
||||
// const [hoverPlay] = useSound("/sfx/s_hover.mp3");
|
||||
|
@ -16,7 +16,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/"
|
||||
className={currentPage === "/" ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage === "/" ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -25,23 +25,31 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<span>Root</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/chat/"
|
||||
className={currentPage.includes("/chat/") ? styles.Main_List_CurrentLink : ""}
|
||||
|
||||
>
|
||||
<div className={`fw ${styles.Main_List_Button}`}>
|
||||
<span className="icon">chat</span>
|
||||
<span>Chat</span>
|
||||
<span className={styles.Main_List_Button_Badge}>NEW</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/jams/"
|
||||
className={currentPage.includes("/jams/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
// onClick={() => currentPage === "/" ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
|
||||
>
|
||||
<div className={`fw ${styles.Main_List_Button}`}>
|
||||
<span className="icon">format_paint</span>
|
||||
<span>Jams</span>
|
||||
<span className={styles.Main_List_Button_Badge}>NEW</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/search/"
|
||||
className={currentPage.includes("/search/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage === "/" ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -53,7 +61,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/projects/"
|
||||
className={currentPage.includes("/projects/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/projects/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -65,7 +73,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/characters/"
|
||||
className={currentPage.includes("/characters/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/characters/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -77,7 +85,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/blog/"
|
||||
className={currentPage.includes("/blog/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/blog/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -89,7 +97,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/gallery/"
|
||||
className={currentPage.includes("/gallery/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/gallery/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -101,7 +109,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/stories/"
|
||||
className={currentPage.includes("/stories/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/stories/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -113,7 +121,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/orgs/"
|
||||
className={currentPage.includes("/orgs/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/orgs/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -125,7 +133,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/links/"
|
||||
className={currentPage.includes("/links/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/links/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -137,7 +145,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/oao/"
|
||||
className={currentPage.includes("/oao/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/oao/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
@ -149,7 +157,7 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<Link
|
||||
href="/about/"
|
||||
className={currentPage.includes("/about/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
|
||||
// onClick={() => currentPage.includes("/about/") ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
|
|
Loading…
Reference in a new issue