starting to put together sockets and two-way communication
This commit is contained in:
parent
cadf1f3075
commit
940fc15b90
6
client/package-lock.json
generated
6
client/package-lock.json
generated
@ -23,6 +23,7 @@
|
|||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2",
|
||||||
"react-sigma": "^1.2.35",
|
"react-sigma": "^1.2.35",
|
||||||
|
"react-use-websocket": "^4.9.0",
|
||||||
"sigma": "^3.0.0-beta.29",
|
"sigma": "^3.0.0-beta.29",
|
||||||
"vite": "^5.4.7",
|
"vite": "^5.4.7",
|
||||||
"vite-tsconfig-paths": "^5.0.1",
|
"vite-tsconfig-paths": "^5.0.1",
|
||||||
@ -3151,6 +3152,11 @@
|
|||||||
"react-dom": ">=15.3"
|
"react-dom": ">=15.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-use-websocket": {
|
||||||
|
"version": "4.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.9.0.tgz",
|
||||||
|
"integrity": "sha512-/6OaCMggQCTnryCAsw/N+/wfH7bBfIXk5WXTMPdyf0x9HWJXLGUVttAT5hqAimRytD1dkHEJCUrFHAGzOAg1eg=="
|
||||||
|
},
|
||||||
"node_modules/redent": {
|
"node_modules/redent": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2",
|
||||||
"react-sigma": "^1.2.35",
|
"react-sigma": "^1.2.35",
|
||||||
|
"react-use-websocket": "^4.9.0",
|
||||||
"sigma": "^3.0.0-beta.29",
|
"sigma": "^3.0.0-beta.29",
|
||||||
"vite": "^5.4.7",
|
"vite": "^5.4.7",
|
||||||
"vite-tsconfig-paths": "^5.0.1",
|
"vite-tsconfig-paths": "^5.0.1",
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
import Landing from '/src/landing/Landing.jsx';
|
import Landing from '/src/landing/Landing.jsx';
|
||||||
import Page from '/src/page/Page.jsx';
|
|
||||||
import PageWithSidebar from '/src/page/PageWithSidebar.jsx';
|
|
||||||
import HistoryView from '/src/page/HistoryView.jsx';
|
import HistoryView from '/src/page/HistoryView.jsx';
|
||||||
import LogIn from '/src/login/LogIn.jsx';
|
import LogIn from '/src/login/LogIn.jsx';
|
||||||
import Register from '/src/login/Register.jsx';
|
import Register from '/src/login/Register.jsx';
|
||||||
import Profile from '/src/login/Profile.jsx';
|
import Profile from '/src/login/Profile.jsx';
|
||||||
|
import GhostPage from '/src/page/GhostPage.jsx';
|
||||||
|
import Live from '/src/embodied/Live.jsx';
|
||||||
|
|
||||||
import AuthProvider from '/src/AuthProvider.jsx';
|
import AuthProvider from '/src/AuthProvider.jsx';
|
||||||
|
|
||||||
@ -23,11 +23,12 @@ function App() {
|
|||||||
<Route path="/" element={<Landing/>}/>
|
<Route path="/" element={<Landing/>}/>
|
||||||
<Route path="/login" element={<LogIn/>}/>
|
<Route path="/login" element={<LogIn/>}/>
|
||||||
<Route path="/register" element={<Register/>}/>
|
<Route path="/register" element={<Register/>}/>
|
||||||
<Route path="/:pagenumber" element={<PageWithSidebar/>}/>
|
<Route path="/:pagenumber" element={<GhostPage/>}/>
|
||||||
<Route path="/:pagenumber/edit" element={<PageWithSidebar editing="true"/>}/>
|
<Route path="/:pagenumber/edit" element={<GhostPage editing="true"/>}/>
|
||||||
<Route path="/:pagenumber/history" element={<HistoryView/>}/>
|
<Route path="/:pagenumber/history" element={<HistoryView/>}/>
|
||||||
<Route path="/:pagenumber/:editid" element={<Page/>}/>
|
<Route path="/:pagenumber/:editid" element={<GhostPage/>}/>
|
||||||
<Route path="/profile" element={<Profile/>}/>
|
<Route path="/profile" element={<Profile/>}/>
|
||||||
|
<Route path="/live" element={<Live/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
@ -3,7 +3,11 @@ import { LogInStatusUpdateEscapeTool } from './AuthProvider.jsx';
|
|||||||
|
|
||||||
// This is wrapper functtions to do requests to the api, from the frontend.
|
// This is wrapper functtions to do requests to the api, from the frontend.
|
||||||
|
|
||||||
export const apiUrl = `${window.location.origin}/api`;
|
const secure = false
|
||||||
|
|
||||||
|
export const apiUrl = `http${secure?'s':''}://${window.location.host}/api`;
|
||||||
|
|
||||||
|
export const wsUrl = `ws${secure?'s':''}://${window.location.host}/api`
|
||||||
|
|
||||||
|
|
||||||
//lil helper to throw errorrs from the promise when we get not-ok results
|
//lil helper to throw errorrs from the promise when we get not-ok results
|
||||||
|
12
client/src/embodied/CommandEntry.jsx
Normal file
12
client/src/embodied/CommandEntry.jsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import './CommandEntry.css';
|
||||||
|
|
||||||
|
function CommandEntry({command, onChange, onSubmit, ...props}) {
|
||||||
|
return (
|
||||||
|
<div className="commandline">
|
||||||
|
<input type="text" placeholder="Enter a command!" value={command} onChange={onChange} {...props}/>
|
||||||
|
<button onClick={() => onSubmit(command)}>Do</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommandEntry;
|
60
client/src/embodied/Live.jsx
Normal file
60
client/src/embodied/Live.jsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { apiUrl, wsUrl } from '../apiTools.jsx';
|
||||||
|
import { useFixLinks } from '../clientStuff.jsx';
|
||||||
|
import { useLoggedIn } from '../AuthProvider.jsx';
|
||||||
|
import useWebSocket, { ReadyState } from 'react-use-websocket';
|
||||||
|
|
||||||
|
import Page from '../page/Page.jsx';
|
||||||
|
import MessageFeed from './MessageFeed.jsx';
|
||||||
|
import Sidebar from './Sidebar.jsx';
|
||||||
|
import CommandEntry from './CommandEntry.jsx';
|
||||||
|
|
||||||
|
function Live({editing, ...props}) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const loggedIn = useLoggedIn();
|
||||||
|
const { pagenumber } = useParams();
|
||||||
|
const [command, setCommand] = useState("");
|
||||||
|
const [ messageHistory, setMessageHistory ] = useState([]);
|
||||||
|
const [ currentNumber, setCurrentNumber ] = useState(1);
|
||||||
|
|
||||||
|
const [ connecting, setConnecting ] = useState(true);
|
||||||
|
const { sendMessage, lastMessage, readyState } = useWebSocket(`${wsUrl}/embody`, {
|
||||||
|
onClose: () => setConnecting(false)
|
||||||
|
}, connecting);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastMessage !== null) {
|
||||||
|
setMessageHistory((prev) => prev.concat(lastMessage));
|
||||||
|
}
|
||||||
|
}, [lastMessage]);
|
||||||
|
|
||||||
|
useEffect(() => { sendMessage("what the fuk is up"); }, []);
|
||||||
|
|
||||||
|
function handleSendMessage() {
|
||||||
|
console.log("sending a message...");
|
||||||
|
sendMessage("button got clicked");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Page editing={editing} number={currentNumber} {...props}/>
|
||||||
|
<MessageFeed messages={messageHistory}>
|
||||||
|
<button disabled={readyState!=ReadyState.OPEN} onClick={handleSendMessage}>send a message to the server!</button>
|
||||||
|
<button disabled={connecting} onClick={() => setConnecting(true)}>Reconnect</button>
|
||||||
|
</MessageFeed>
|
||||||
|
<Sidebar pagenumber={pagenumber} hidden="true" sendWord={(word) => setCommand((command + " " + word).trim())}>
|
||||||
|
{!editing && <li><button onClick={() => navigate(`/${pagenumber}/edit`)}>edit</button></li>}
|
||||||
|
{editing && <li><button disabled={postMutation.isPending} onClick={submitChanges}>save</button></li>}
|
||||||
|
{editing && <li><button onClick={() => navigate(`/${pagenumber}`)}>return</button></li>}
|
||||||
|
</Sidebar>
|
||||||
|
<CommandEntry
|
||||||
|
command={command}
|
||||||
|
onChange={(e) => setCommand(e.target.value)}
|
||||||
|
onSubmit={(e) => { setCommand(""); sendMessage(e); }}/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Live;
|
25
client/src/embodied/MessageFeed.css
Normal file
25
client/src/embodied/MessageFeed.css
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
.messages-tray {
|
||||||
|
transition: all 0.1s linear;
|
||||||
|
margin: 0;
|
||||||
|
background: lightblue;
|
||||||
|
position: fixed;
|
||||||
|
width: 30ch;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.messages-hidden {
|
||||||
|
transform: translateX(-20ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li button {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-hidden li {
|
||||||
|
display: none;
|
||||||
|
}
|
27
client/src/embodied/MessageFeed.jsx
Normal file
27
client/src/embodied/MessageFeed.jsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import './MessageFeed.css';
|
||||||
|
|
||||||
|
function MessageFeed({messages=[], children}) {
|
||||||
|
const [ open, setOpen ] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`messages-tray ${!open?'messages-hidden':''}`}>
|
||||||
|
<button style={{float: 'right'}} onClick={() => setOpen(!open)}>{open?'Hide':'Show'}</button>
|
||||||
|
<h2>Message history:</h2>
|
||||||
|
<ol>
|
||||||
|
{messages.map((message, idx) =>
|
||||||
|
<li key={idx}>
|
||||||
|
{message.data}
|
||||||
|
<code style={{background: "lightgrey"}}>{JSON.stringify(message)}</code>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ol>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MessageFeed;
|
@ -48,6 +48,7 @@ function Landing() {
|
|||||||
<button onClick={makeNewPage.mutate}>Dig new room!</button><br/>
|
<button onClick={makeNewPage.mutate}>Dig new room!</button><br/>
|
||||||
<button onClick={postLogOut}>Log Out</button><br/>
|
<button onClick={postLogOut}>Log Out</button><br/>
|
||||||
<button onClick={() => navigate('/profile')}>Profile</button><br/>
|
<button onClick={() => navigate('/profile')}>Profile</button><br/>
|
||||||
|
<button onClick={() => navigate('/live')}>Embody!</button><br/>
|
||||||
</>
|
</>
|
||||||
:
|
:
|
||||||
<>
|
<>
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { fetchProfile } from '../apiTools.jsx';
|
import { fetchProfile } from '../apiTools.jsx';
|
||||||
import { useLoggedIn } from '../AuthProvider.jsx';
|
import { useLoggedIn } from '../AuthProvider.jsx';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import useWebSocket, { ReadyState } from 'react-use-websocket';
|
||||||
|
|
||||||
function Profile() {
|
function Profile() {
|
||||||
const { register, handleSubmit } = useForm();
|
const { register, handleSubmit } = useForm();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const loggedIn = useLoggedIn();
|
const loggedIn = useLoggedIn();
|
||||||
|
useEffect(() => { if (!loggedIn) navigate('/'); }, [loggedIn]);
|
||||||
|
|
||||||
const { isPending, isError, error, data } = useQuery({ // fetch the currrent values
|
const { isPending, isError, error, data } = useQuery({ // fetch the currrent values
|
||||||
queryKey: ['profile'],
|
queryKey: ['profile'],
|
||||||
queryFn: fetchProfile,
|
queryFn: fetchProfile,
|
||||||
retry: 1
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => { if (!loggedIn) navigate('/'); }, [loggedIn]);
|
|
||||||
|
|
||||||
const { id, name, favoriteColor, leastFavoriteColor } = (data || {});
|
const { id, name, favoriteColor, leastFavoriteColor } = (data || {});
|
||||||
|
const playerObject = 30;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="main-column">
|
<div className="main-column">
|
||||||
<section className="page-contents">
|
<section className="page-contents">
|
||||||
|
<h2>Profile page</h2>
|
||||||
<p>the page {isPending ? "is" : "isn't"} pending</p>
|
<p>the page {isPending ? "is" : "isn't"} pending</p>
|
||||||
<p>there {isError ? "is" : "isn't"} an error</p>
|
<p>there {isError ? "is" : "isn't"} an error</p>
|
||||||
<p>error is {error?.message}</p>
|
<p>error is {error?.message}</p>
|
||||||
@ -47,6 +47,11 @@ function Profile() {
|
|||||||
<input {...register("leastFavoriteColor")} value={leastFavoriteColor}/>
|
<input {...register("leastFavoriteColor")} value={leastFavoriteColor}/>
|
||||||
<br/>
|
<br/>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
Player object:
|
||||||
|
<input {...register("playerObject")} value={playerObject} disabled/>
|
||||||
|
<br/>
|
||||||
|
</label>
|
||||||
<button type="submit">Change those settings, bucko!!</button>
|
<button type="submit">Change those settings, bucko!!</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import './CommandEntry.css';
|
|
||||||
|
|
||||||
function CommandEntry({command, onChange}) {
|
|
||||||
return (
|
|
||||||
<div className="commandline">
|
|
||||||
<input type="text" placeholder="Enter a command!" value={command} onChange={onChange}/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CommandEntry;
|
|
19
client/src/page/GhostPage.jsx
Normal file
19
client/src/page/GhostPage.jsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { apiUrl, fetchPage, fetchPageAtEdit, postPage, deletePage } from '../apiTools.jsx';
|
||||||
|
import { useFixLinks } from '../clientStuff.jsx';
|
||||||
|
import { useLoggedIn } from '../AuthProvider.jsx';
|
||||||
|
import Page from './Page.jsx';
|
||||||
|
|
||||||
|
import './Pages.css';
|
||||||
|
|
||||||
|
function GhostPage({editing, ...props}) {
|
||||||
|
const { pagenumber } = useParams();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page number={pagenumber} editing={editing} {...props}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GhostPage;
|
@ -6,10 +6,11 @@ import { useLoggedIn } from '../AuthProvider.jsx';
|
|||||||
|
|
||||||
import './Pages.css';
|
import './Pages.css';
|
||||||
|
|
||||||
function Page({ editing }) {
|
function Page({ editing, number }) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pagenumber, editid } = useParams();
|
const { editid } = useParams();
|
||||||
|
const pagenumber = number
|
||||||
const loggedIn = useLoggedIn();
|
const loggedIn = useLoggedIn();
|
||||||
const noLoad = useFixLinks();
|
const noLoad = useFixLinks();
|
||||||
|
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
|
||||||
import { apiUrl, fetchPage, fetchPageAtEdit, postPage, deletePage } from '../apiTools.jsx';
|
|
||||||
import { useFixLinks } from '../clientStuff.jsx';
|
|
||||||
import { useLoggedIn } from '../AuthProvider.jsx';
|
|
||||||
import Sidebar from './Sidebar.jsx';
|
|
||||||
import CommandEntry from './CommandEntry.jsx';
|
|
||||||
|
|
||||||
import './Pages.css';
|
|
||||||
|
|
||||||
function PageWithSidebar({ editing }) {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { pagenumber, editid } = useParams();
|
|
||||||
const loggedIn = useLoggedIn();
|
|
||||||
const noLoad = useFixLinks();
|
|
||||||
const [command, setCommand] = useState("");
|
|
||||||
|
|
||||||
const fetchQuery = useQuery({ // fetch the currrent values
|
|
||||||
queryKey: ['page', pagenumber, editid],
|
|
||||||
queryFn: () => editid ? fetchPageAtEdit(pagenumber, editid) : fetchPage(pagenumber)
|
|
||||||
})
|
|
||||||
|
|
||||||
const postMutation = useMutation({ // for changing the value when we're done with it
|
|
||||||
mutationFn: ({id, title, description}) => postPage({id, title, description}),
|
|
||||||
onSettled: async (data, error, variables) => {
|
|
||||||
// Invalidate and refetch
|
|
||||||
await queryClient.invalidateQueries({ queryKey: ['page', variables.id, undefined] })
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const deleteMutation = useMutation({ // for changing the value when we're done with it
|
|
||||||
mutationFn: (id) => deletePage(id),
|
|
||||||
onSettled: async (data, error, variables) => {
|
|
||||||
// Invalidate and refetch
|
|
||||||
await queryClient.invalidateQueries({ queryKey: ['pages'] })
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const readyToShow = !(fetchQuery.error || fetchQuery.isPending);
|
|
||||||
|
|
||||||
let {id, title, description, html, time, author} = fetchQuery.data || {};
|
|
||||||
if (!title) title = "[no title]";
|
|
||||||
if (!html) html = "[body missing]";
|
|
||||||
if (!description) description = "[body missing]";
|
|
||||||
|
|
||||||
function submitChanges(e) {
|
|
||||||
const newTitle = document.querySelector('span').innerHTML;
|
|
||||||
const newText = document.querySelector('pre').innerHTML;
|
|
||||||
postMutation.mutate({
|
|
||||||
id: pagenumber,
|
|
||||||
title: newTitle,
|
|
||||||
description: newText
|
|
||||||
});
|
|
||||||
navigate(`/${pagenumber}`, {replace: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitDelete(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
deleteMutation.mutate(pagenumber);
|
|
||||||
navigate(`/`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="main-column">
|
|
||||||
<header>
|
|
||||||
<h1>
|
|
||||||
<a href="/" {...noLoad}>🌳</a>
|
|
||||||
{pagenumber}.
|
|
||||||
<span
|
|
||||||
contentEditable={editing}
|
|
||||||
dangerouslySetInnerHTML={{__html: readyToShow ? title : "..." }} />
|
|
||||||
</h1>
|
|
||||||
{readyToShow && editid && `saved at ${time} by user #${author}`}
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
<section className="page-contents">
|
|
||||||
{ readyToShow ?
|
|
||||||
(
|
|
||||||
editing ?
|
|
||||||
<pre
|
|
||||||
contentEditable="true"
|
|
||||||
dangerouslySetInnerHTML={{__html: description}} />
|
|
||||||
:
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{__html: html}}
|
|
||||||
{...noLoad} />
|
|
||||||
)
|
|
||||||
:
|
|
||||||
"..."
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
<button
|
|
||||||
onClick={() => navigate(`/${pagenumber}/history`)}>
|
|
||||||
History
|
|
||||||
</button>
|
|
||||||
{editing && (
|
|
||||||
<button
|
|
||||||
disabled={postMutation.isPending}
|
|
||||||
onClick={submitChanges}>
|
|
||||||
{postMutation.isPending ? "Updating..." : "Update"}
|
|
||||||
</button>)}
|
|
||||||
{!editing && !editid && (
|
|
||||||
<button
|
|
||||||
disabled={!loggedIn}
|
|
||||||
onClick={() => navigate(`/${pagenumber}/edit`)}>
|
|
||||||
Edit Page
|
|
||||||
</button>)}
|
|
||||||
{loggedIn && (
|
|
||||||
<button
|
|
||||||
disabled={deleteMutation.isPending}
|
|
||||||
onClick={submitDelete}>
|
|
||||||
{deleteMutation.isPending ? "Deleting..." : "Delete this page and entire edit history (no backsies)"}
|
|
||||||
</button>)}
|
|
||||||
</div>
|
|
||||||
<Sidebar pagenumber={pagenumber} hidden="true" sendWord={(word) => setCommand((command + " " + word).trim())}>
|
|
||||||
{!editing && <li><button onClick={() => navigate(`/${pagenumber}/edit`)}>edit</button></li>}
|
|
||||||
{editing && <li><button disabled={postMutation.isPending} onClick={submitChanges}>save</button></li>}
|
|
||||||
{editing && <li><button onClick={() => navigate(`/${pagenumber}`)}>return</button></li>}
|
|
||||||
</Sidebar>
|
|
||||||
<CommandEntry command={command} onChange={(e) => setCommand(e.target.value)}/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PageWithSidebar;
|
|
@ -1,5 +1,5 @@
|
|||||||
function loginRequired(req, res, next) {
|
function loginRequired(req, res, next) {
|
||||||
console.log("checkinig on req.session for auhetnticaion: ", req.session);
|
//console.log("checkinig on req.session for auhetnticaion: ", req.session);
|
||||||
if (!req.session.name) {
|
if (!req.session.name) {
|
||||||
return res.status(401).json({"error": "need to be logged in for that bucko"});
|
return res.status(401).json({"error": "need to be logged in for that bucko"});
|
||||||
}
|
}
|
||||||
|
47
server/interpreter.js
Normal file
47
server/interpreter.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const sqlite = require('better-sqlite3');
|
||||||
|
const db = new sqlite('the_big_db.db', { verbose: console.log });
|
||||||
|
|
||||||
|
var sockets = new Map();
|
||||||
|
|
||||||
|
function interpret(context, subject, command) {
|
||||||
|
const words = command.split(' ');
|
||||||
|
|
||||||
|
const verbs = findVerbs(context, subject);
|
||||||
|
// first word is either a subject or a verb. either way there must be a verb.
|
||||||
|
// check if the first word is in the list of verbs.
|
||||||
|
const [first, second, third, ...rest] = words;
|
||||||
|
if (second in verbs) {
|
||||||
|
executeVerb(verbs.get(second), first, third, ...rest);
|
||||||
|
} else {
|
||||||
|
executeVerb(verbs.get(first), subject, second, third, ...rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findVerbs(location, actor) {
|
||||||
|
// check for verbs on actor
|
||||||
|
// check for verbs on objects in location
|
||||||
|
// check for verbs on location
|
||||||
|
const out = new Map();
|
||||||
|
out.set("look", 29);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeVerb(verb, subject, object, ...rest) {
|
||||||
|
lookUpObject(verb).fn(subject, object, ...rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectQuery = db.prepare('select * from pages where id=?');
|
||||||
|
function lookUpObject(id) {
|
||||||
|
// return objectQuery.get(id);
|
||||||
|
if (id == 30) return {name: "shoofle", contents: "this is a shoofle", location: 1};
|
||||||
|
if (id == 29) return {
|
||||||
|
name: "look",
|
||||||
|
contents: "send description of direct object to subject's socket",
|
||||||
|
fn: (subject, object, ...rest) => {
|
||||||
|
sockets.get(subject)?.send(`you looked around! subject: ${subject} object: ${object} args: ${rest}`);
|
||||||
|
console.log(`${subject} looked at ${object} with args ${rest}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { interpret, sockets, lookUpObject };
|
35
server/package-lock.json
generated
35
server/package-lock.json
generated
@ -16,6 +16,7 @@
|
|||||||
"client-sessions": "^0.8.0",
|
"client-sessions": "^0.8.0",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"express-ws": "^5.0.2",
|
||||||
"graphology": "^0.25.4",
|
"graphology": "^0.25.4",
|
||||||
"graphology-layout": "^0.6.1",
|
"graphology-layout": "^0.6.1",
|
||||||
"graphology-layout-force": "^0.2.4",
|
"graphology-layout-force": "^0.2.4",
|
||||||
@ -815,6 +816,40 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
|
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/express-ws": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^7.4.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": "^4.0.0 || ^5.0.0-alpha.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-ws/node_modules/ws": {
|
||||||
|
"version": "7.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||||
|
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fetch-blob": {
|
"node_modules/fetch-blob": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"client-sessions": "^0.8.0",
|
"client-sessions": "^0.8.0",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"express-ws": "^5.0.2",
|
||||||
"graphology": "^0.25.4",
|
"graphology": "^0.25.4",
|
||||||
"graphology-layout": "^0.6.1",
|
"graphology-layout": "^0.6.1",
|
||||||
"graphology-layout-force": "^0.2.4",
|
"graphology-layout-force": "^0.2.4",
|
||||||
|
@ -12,6 +12,9 @@ app.use(page_routes);
|
|||||||
const user_routes = require('./users.js');
|
const user_routes = require('./users.js');
|
||||||
app.use(user_routes);
|
app.use(user_routes);
|
||||||
|
|
||||||
|
const socket_routes = require('./sockets.js');
|
||||||
|
app.use(socket_routes);
|
||||||
|
|
||||||
const graphQuery = db.prepare(`
|
const graphQuery = db.prepare(`
|
||||||
select p.number, p.html, p.time
|
select p.number, p.html, p.time
|
||||||
from pages p
|
from pages p
|
||||||
|
42
server/routes/sockets.js
Normal file
42
server/routes/sockets.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const app = express.Router();
|
||||||
|
const expressWs = require('express-ws')(app);
|
||||||
|
|
||||||
|
const sqlite = require('better-sqlite3');
|
||||||
|
const db = new sqlite('the_big_db.db', { verbose: console.log });
|
||||||
|
|
||||||
|
const { loginRequired } = require('../authStuff.js');
|
||||||
|
|
||||||
|
const { interpret, sockets, lookUpObject } = require('../interpreter.js');
|
||||||
|
|
||||||
|
const clockListeners = new Set();
|
||||||
|
const clock = setInterval(() => {
|
||||||
|
if (clockListeners.size == 0) return;
|
||||||
|
console.log(`sending a ping to all ${clockListeners.size} connections`);
|
||||||
|
clockListeners.forEach((x) => x());
|
||||||
|
}, 5000);
|
||||||
|
console.log(`set up the clock: ${clock}`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.ws('/embody', (ws, req) => {
|
||||||
|
// const { playerObject } = db.prepare('select playerObject from users where id=?').get(req.session.userId)
|
||||||
|
const playerObjectId = 30; // mocked out for now!
|
||||||
|
sockets.set(playerObjectId, ws);
|
||||||
|
|
||||||
|
ws.on('message', (msg) => {
|
||||||
|
const location = lookUpObject(playerObjectId).location;
|
||||||
|
|
||||||
|
interpret(location, playerObjectId, msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => sockets.delete(playerObjectId));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = app;
|
@ -29,6 +29,7 @@ app.post('/register', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/login', async (req, res) => {
|
app.post('/login', async (req, res) => {
|
||||||
|
console.log(req.body);
|
||||||
if (req.session.name) {
|
if (req.session.name) {
|
||||||
return res.status(200).json({message: "already logged in", name: req.session.name});
|
return res.status(200).json({message: "already logged in", name: req.session.name});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const expressWs = require('express-ws')(app);
|
||||||
|
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
|
|
||||||
@ -8,7 +9,7 @@ const apiRoutes = require('./routes/api.js');
|
|||||||
const port = process.env.PORT || 3001; // Use the port provided by the host or default to 3000
|
const port = process.env.PORT || 3001; // Use the port provided by the host or default to 3000
|
||||||
|
|
||||||
const sqlite = require('better-sqlite3');
|
const sqlite = require('better-sqlite3');
|
||||||
const db = new sqlite('the_big_db.db', { verbose: console.log });
|
const db = new sqlite('the_big_db.db');
|
||||||
const SqliteStore = require('better-sqlite3-session-store')(session);
|
const SqliteStore = require('better-sqlite3-session-store')(session);
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
@ -5,6 +5,11 @@ server {
|
|||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://localhost:3001/api/;
|
proxy_pass http://localhost:3001/api/;
|
||||||
|
|
||||||
|
# WebSocket support
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
}
|
}
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:3000/;
|
proxy_pass http://localhost:3000/;
|
||||||
|
Loading…
Reference in New Issue
Block a user