fixing up live display and mobile, making custom swipe drawer
This commit is contained in:
parent
9575d333ad
commit
90d8ec5da9
1661
client/package-lock.json
generated
1661
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -34,7 +34,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@ -53,5 +54,9 @@
|
|||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"jsdom": "^25.0.1",
|
||||||
|
"vitest": "^2.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ a:visited {
|
|||||||
max-width: 76ch;
|
max-width: 76ch;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding-top: 3rem;
|
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
|
||||||
render(<App />);
|
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
36
client/src/components/Shim.jsx
Normal file
36
client/src/components/Shim.jsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
function Shim({height, width, duplicateOf}) {
|
||||||
|
const shimDiv = useRef(null);
|
||||||
|
|
||||||
|
let target = duplicateOf.current;
|
||||||
|
let shim = shimDiv.current;
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if (target && shim) {
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
const compStyle= window.getComputedStyle(target)
|
||||||
|
const margins = {
|
||||||
|
top: compStyle.getPropertyValue('margin-top'),
|
||||||
|
bottom: compStyle.getPropertyValue('margin-bottom'),
|
||||||
|
left: compStyle.getPropertyValue('margin-left'),
|
||||||
|
right: compStyle.getPropertyValue('margin-right'),
|
||||||
|
}
|
||||||
|
if (width) {
|
||||||
|
shim.style.width = `calc(${margins.left} + ${margins.right} + ${target.offsetWidth}px)`;
|
||||||
|
}
|
||||||
|
if (height) {
|
||||||
|
shim.style.height = `calc(${margins.top} + ${margins.bottom} + ${target.offsetHeight}px)`;
|
||||||
|
}
|
||||||
|
console.log('set shimdiv style', rect, margins);
|
||||||
|
}
|
||||||
|
}, [ target, shim,
|
||||||
|
width, height,
|
||||||
|
target && target.offsetWidth,
|
||||||
|
target && target.offsetHeight ]
|
||||||
|
);
|
||||||
|
|
||||||
|
return <div ref={shimDiv}><h4>I'M A SHIM</h4></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Shim;
|
43
client/src/components/SwipeableDrawer.css
Normal file
43
client/src/components/SwipeableDrawer.css
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.swipeTargetStyle {
|
||||||
|
width: 30vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeTargetStyle-left {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeTargetStyle-right {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawerStyle {
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
width: 30vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
background: lightgreen;
|
||||||
|
}
|
||||||
|
.drawerStyle-left {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawerStyle-left-closed {
|
||||||
|
transform: translateX(-30vw);
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawerStyle-right {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawerStyle-right-closed {
|
||||||
|
transform: translateX(30vw);
|
||||||
|
background: green;
|
||||||
|
}
|
115
client/src/components/SwipeableDrawer.jsx
Normal file
115
client/src/components/SwipeableDrawer.jsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { useState, useRef } from 'react';
|
||||||
|
|
||||||
|
import { Button, IconButton } from '@mui/material';
|
||||||
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
|
import './SwipeableDrawer.css';
|
||||||
|
|
||||||
|
function SwipeableDrawer({children, anchor="right", hidden=true, className, ...props}) {
|
||||||
|
const [ open, setOpen ] = useState(!hidden);
|
||||||
|
|
||||||
|
const [ distance, setDistance ] = useState(0);
|
||||||
|
const [ origin, setOrigin ] = useState(0);
|
||||||
|
const [ inTouch, setInTouch ] = useState(false);
|
||||||
|
|
||||||
|
const swipeTargetRef = useRef(null);
|
||||||
|
|
||||||
|
function onTouchStart(e) {
|
||||||
|
setInTouch(true);
|
||||||
|
setOrigin(e.targetTouches[0].clientX);
|
||||||
|
}
|
||||||
|
function onTouchMove(e) {
|
||||||
|
setDistance(e.targetTouches[0].clientX - origin);
|
||||||
|
}
|
||||||
|
function onTouchEnd(e) {
|
||||||
|
let threshhold = 100;
|
||||||
|
if (swipeTargetRef.current) {
|
||||||
|
threshhold = swipeTargetRef.current.getBoundingClientRect().width * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchor == "right") {
|
||||||
|
if (distance < -threshhold) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
if (distance > threshhold) {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchor == "left") {
|
||||||
|
if (distance > threshhold) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
if (distance < -threshhold) {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setOrigin(0);
|
||||||
|
setDistance(0);
|
||||||
|
setInTouch(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragStyle = {};
|
||||||
|
if (inTouch) {
|
||||||
|
dragStyle.transition = "none";
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
dragStyle.transform = `translateX(${distance}px)`;
|
||||||
|
} else {
|
||||||
|
if (anchor == "right") dragStyle.transform = `translateX(calc(30vw + ${distance}px))`;
|
||||||
|
if (anchor == "left") dragStyle.transform = `translateX(calc(-30vw + ${distance}px))`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawerClosedClass = open ? "" : `drawerStyle-${anchor}-closed`;
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
<div
|
||||||
|
ref={swipeTargetRef}
|
||||||
|
className={`swipeTargetStyle swipeTargetStyle-${anchor}`}
|
||||||
|
onTouchStart={onTouchStart}
|
||||||
|
onTouchMove={onTouchMove}
|
||||||
|
onTouchEnd={onTouchEnd}>
|
||||||
|
<IconButton
|
||||||
|
size="large"
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
visible: open,
|
||||||
|
padding: "2rem",
|
||||||
|
position: "absolute",
|
||||||
|
right: anchor == "right" ? 0 : "auto",
|
||||||
|
left: anchor == "left" ? 0 : "auto",
|
||||||
|
top: 0
|
||||||
|
}}
|
||||||
|
onClick={() => setOpen(true)} >
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`drawerStyle drawerStyle-${anchor} ${drawerClosedClass} ${className}`}
|
||||||
|
style={dragStyle}
|
||||||
|
onTouchStart={onTouchStart}
|
||||||
|
onTouchMove={onTouchMove}
|
||||||
|
onTouchEnd={onTouchEnd}>
|
||||||
|
<IconButton
|
||||||
|
size="large"
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
visible: open,
|
||||||
|
padding: "2rem",
|
||||||
|
position: "absolute",
|
||||||
|
right: anchor == "left" ? 0 : "auto",
|
||||||
|
left: anchor == "right" ? 0 : "auto",
|
||||||
|
top: 0
|
||||||
|
}}
|
||||||
|
onClick={() => setOpen(false)} >
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SwipeableDrawer;
|
15
client/src/components/SwipeableDrawer.test.jsx
Normal file
15
client/src/components/SwipeableDrawer.test.jsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { describe, expect, test, assert } from 'vitest';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import SwipeableDrawer from './SwipeableDrawer.jsx';
|
||||||
|
|
||||||
|
//TODO: i was going to write tests for things but then i felt silly writing a single test file
|
||||||
|
// so i guess i'm not going too write tests at the moment
|
||||||
|
// sorry for my sin against good software development practices
|
||||||
|
|
||||||
|
describe('swipeable drawer tests', () => {
|
||||||
|
test("test that it renders its children", () => {
|
||||||
|
render(<SwipeableDrawer><h4>Content</h4></SwipeableDrawer>);
|
||||||
|
|
||||||
|
expect(screen.getByText(/Content/i)).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
@ -6,25 +6,27 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
background: lightgreen;
|
background: lightgreen;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 1rem;
|
||||||
left: 20%;
|
left: 20%;
|
||||||
right: 20%;
|
right: 20%;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
}
|
|
||||||
.commandline form {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 100%;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commandline input {
|
.commandline input {
|
||||||
flex: 10;
|
flex: 1;
|
||||||
font-size: 16pt;
|
font-size: 16pt;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commandline button {
|
.commandline button {
|
||||||
|
border-radius: 4rem;
|
||||||
|
width: 4rem;
|
||||||
}
|
}
|
@ -1,21 +1,21 @@
|
|||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { Fab } from '@mui/material';
|
|
||||||
|
|
||||||
import './CommandEntry.css';
|
import './CommandEntry.css';
|
||||||
|
|
||||||
const CommandEntry = forwardRef(({ setCommand, onSubmitCommand, command }, ref) => {
|
const CommandEntry = forwardRef(({ setCommand, onSubmitCommand, command }, ref) => {
|
||||||
return (
|
return (
|
||||||
<div className="commandline" >
|
<div className="commandline" ref={ref} >
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
id="command_input"
|
||||||
onChange={(e) => setCommand(e.target.value)}
|
onChange={(e) => setCommand(e.target.value)}
|
||||||
autoFocus
|
autoFocus
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter a command!"
|
placeholder="Enter a command!"
|
||||||
value={command} />
|
value={command} />
|
||||||
<Fab onClick={ () => { console.log("clicked"); onSubmitCommand(command); }} >
|
<button
|
||||||
|
onClick={ () => onSubmitCommand(command) } >
|
||||||
Send
|
Send
|
||||||
</Fab>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,7 @@ import { JsonEditor } from 'json-edit-react';
|
|||||||
import MessageFeed from './MessageFeed.jsx';
|
import MessageFeed from './MessageFeed.jsx';
|
||||||
import WordSidebar from './WordSidebar.jsx';
|
import WordSidebar from './WordSidebar.jsx';
|
||||||
import CommandEntry from './CommandEntry.jsx';
|
import CommandEntry from './CommandEntry.jsx';
|
||||||
|
import Shim from '../components/Shim.jsx';
|
||||||
|
|
||||||
function Live({...props}) {
|
function Live({...props}) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -22,7 +23,6 @@ function Live({...props}) {
|
|||||||
const [ editing, setEditing ] = useState(false);
|
const [ editing, setEditing ] = useState(false);
|
||||||
const [ connecting, setConnecting ] = useState(true);
|
const [ connecting, setConnecting ] = useState(true);
|
||||||
|
|
||||||
const editorRef = useRef(null);
|
|
||||||
const commandEntryRef = useRef(null);
|
const commandEntryRef = useRef(null);
|
||||||
|
|
||||||
//setting up the websocket and using its data!
|
//setting up the websocket and using its data!
|
||||||
@ -61,6 +61,8 @@ function Live({...props}) {
|
|||||||
setType(fetchPageQuery.data?.type == 1);
|
setType(fetchPageQuery.data?.type == 1);
|
||||||
}, [fetchPageQuery.data?.type]);
|
}, [fetchPageQuery.data?.type]);
|
||||||
|
|
||||||
|
const editorRef = useRef(null);
|
||||||
|
|
||||||
// extended attribute store!
|
// extended attribute store!
|
||||||
const fetchAttributesQuery = useQuery({
|
const fetchAttributesQuery = useQuery({
|
||||||
queryKey: ['attributes', currentNumber],
|
queryKey: ['attributes', currentNumber],
|
||||||
@ -72,7 +74,6 @@ function Live({...props}) {
|
|||||||
setAttributes(fetchAttributesQuery.data);
|
setAttributes(fetchAttributesQuery.data);
|
||||||
}, [fetchAttributesQuery.data]);
|
}, [fetchAttributesQuery.data]);
|
||||||
|
|
||||||
console.log("is live reloading wrking");
|
|
||||||
// verbs available to us
|
// verbs available to us
|
||||||
const fetchVerbsQuery = useQuery({
|
const fetchVerbsQuery = useQuery({
|
||||||
queryKey: ['my verbs'],
|
queryKey: ['my verbs'],
|
||||||
@ -169,22 +170,26 @@ function Live({...props}) {
|
|||||||
<ExtendedAttributes
|
<ExtendedAttributes
|
||||||
attributes={attributes} />
|
attributes={attributes} />
|
||||||
}
|
}
|
||||||
|
<Shim height={true} width={false} duplicateOf={commandEntryRef}/>
|
||||||
</div>
|
</div>
|
||||||
<MessageFeed messages={messageHistory}>
|
<MessageFeed messages={messageHistory}>
|
||||||
<button disabled={connecting} onClick={() => setConnecting(true)}>
|
<p>
|
||||||
{{
|
{{
|
||||||
[ReadyState.CONNECTING]: 'Connecting',
|
[ReadyState.CONNECTING]: 'Connecting',
|
||||||
[ReadyState.OPEN]: 'Open',
|
[ReadyState.OPEN]: 'Open',
|
||||||
[ReadyState.CLOSING]: 'Closing',
|
[ReadyState.CLOSING]: 'Closing',
|
||||||
[ReadyState.CLOSED]: 'Closed',
|
[ReadyState.CLOSED]: 'Closed',
|
||||||
[ReadyState.UNINSTANTIATED]: 'Uninstantiated',
|
[ReadyState.UNINSTANTIATED]: 'Uninstantiated',
|
||||||
}[readyState]}</button>
|
}[readyState]}
|
||||||
|
</p>
|
||||||
<button onClick={()=> setMessageHistory([])}>Clear History</button>
|
<button onClick={()=> setMessageHistory([])}>Clear History</button>
|
||||||
</MessageFeed>
|
</MessageFeed>
|
||||||
<WordSidebar verbs={verbs} sendWord={(word) => {
|
<WordSidebar
|
||||||
|
verbs={verbs}
|
||||||
|
sendWord={(word) => {
|
||||||
setCommand((command + " " + word).trim());
|
setCommand((command + " " + word).trim());
|
||||||
commandEntryRef.current.focus();
|
// diong this outside of react so shim can... ugh
|
||||||
// maybe set focus to the command entry?
|
document.getElementById("command_input").focus();
|
||||||
}}>
|
}}>
|
||||||
</WordSidebar>
|
</WordSidebar>
|
||||||
<CommandEntry
|
<CommandEntry
|
||||||
|
@ -1,28 +1,19 @@
|
|||||||
.messages-tray {
|
.messages-tray {
|
||||||
display: none;/*flex;*/
|
display: flex;/*flex;*/
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transition: all 0.1s linear;
|
|
||||||
margin: 0;
|
|
||||||
background: lightgreen;
|
|
||||||
position: fixed;
|
|
||||||
width: 30ch;
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.messages-hidden {
|
|
||||||
transform: translateX(-20ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages-tray ol {
|
.messages-tray ol {
|
||||||
flex: 10;
|
flex: 10;
|
||||||
padding-left: 1ch;
|
padding-left: 1ch;
|
||||||
overflow: auto;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-tray li {
|
.messages-tray li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-tray li:nth-child(even) {
|
.messages-tray li:nth-child(even) {
|
||||||
background: lightgray;
|
background: lightgray;
|
||||||
}
|
}
|
@ -1,45 +1,21 @@
|
|||||||
import { useEffect, useState, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useForm } from "react-hook-form";
|
import SwipeableDrawer from '../components/SwipeableDrawer.jsx';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { IconButton, SwipeableDrawer } from '@mui/material';
|
|
||||||
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
|
||||||
|
|
||||||
import './MessageFeed.css';
|
import './MessageFeed.css';
|
||||||
|
|
||||||
function MessageFeed({messages=[], children}) {
|
function MessageFeed({messages=[], hidden=true, children}) {
|
||||||
const [ open, setOpen ] = useState(false);
|
|
||||||
const listContainer = useRef(null);
|
const listContainer = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (listContainer.current)
|
if (listContainer.current)
|
||||||
listContainer.current.scrollTo(0, listContainer.current.scrollHeight);
|
listContainer.current.scrollTo(0, listContainer.current.scrollHeight);
|
||||||
}, [listContainer, messages])
|
}, [listContainer.current, messages])
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
<IconButton
|
|
||||||
size="large"
|
|
||||||
style={{
|
|
||||||
visible: open,
|
|
||||||
position: "fixed",
|
|
||||||
padding: "2rem",
|
|
||||||
left: 0,
|
|
||||||
top: 0
|
|
||||||
}}
|
|
||||||
onClick={() => setOpen(true)} >
|
|
||||||
<MenuIcon />
|
|
||||||
</IconButton>
|
|
||||||
<SwipeableDrawer
|
<SwipeableDrawer
|
||||||
anchor="left"
|
anchor="left"
|
||||||
open={open}
|
hidden={hidden}
|
||||||
onClose={() => setOpen(false)}
|
className="messages-tray">
|
||||||
onOpen={() => setOpen(true)}>
|
|
||||||
<IconButton
|
|
||||||
variant="text"
|
|
||||||
onClick={() => setOpen(false)} >
|
|
||||||
<CloseIcon />
|
|
||||||
</IconButton>
|
|
||||||
<h2>Message history:</h2>
|
<h2>Message history:</h2>
|
||||||
<ol ref={listContainer}>
|
<ol ref={listContainer}>
|
||||||
{messages.map((message, idx) =>
|
{messages.map((message, idx) =>
|
||||||
@ -50,7 +26,7 @@ function MessageFeed({messages=[], children}) {
|
|||||||
</ol>
|
</ol>
|
||||||
{children}
|
{children}
|
||||||
</SwipeableDrawer>
|
</SwipeableDrawer>
|
||||||
</>);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MessageFeed;
|
export default MessageFeed;
|
@ -1,25 +0,0 @@
|
|||||||
.sidebar {
|
|
||||||
transition: all 0.1s linear;
|
|
||||||
margin: 0;
|
|
||||||
background: lightgreen;
|
|
||||||
position: fixed;
|
|
||||||
width: 30ch;
|
|
||||||
height: 100%;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.sidebar-hidden {
|
|
||||||
transform: translateX(20ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar li {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar li button {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-hidden li {
|
|
||||||
display: none;
|
|
||||||
}
|
|
17
client/src/embodied/WordSidebar.css
Normal file
17
client/src/embodied/WordSidebar.css
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.sidebar {
|
||||||
|
}
|
||||||
|
.sidebar-hidden {
|
||||||
|
transform: translateX(20ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar ul {
|
||||||
|
padding-left: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li button {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
@ -1,52 +1,22 @@
|
|||||||
import { useState } from 'react';
|
import SwipeableDrawer from '../components/SwipeableDrawer.jsx';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useLoggedIn } from '../AuthProvider.jsx';
|
|
||||||
import { fetchCurrentVerbs } from '../apiTools.jsx';
|
|
||||||
import { Button, IconButton, SwipeableDrawer } from '@mui/material';
|
|
||||||
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import './WordSidebar.css';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
|
||||||
|
|
||||||
import './Sidebar.css';
|
|
||||||
|
|
||||||
function WordSidebar({children, verbs, hidden=true, sendWord=(()=>null)}) {
|
function WordSidebar({children, verbs, hidden=true, sendWord=(()=>null)}) {
|
||||||
const [open, setOpen] = useState(!hidden);
|
|
||||||
const loggedIn = useLoggedIn();
|
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<IconButton
|
|
||||||
size="large"
|
|
||||||
variant="contained"
|
|
||||||
style={{
|
|
||||||
visible: open,
|
|
||||||
position: "fixed",
|
|
||||||
padding: "2rem",
|
|
||||||
right: 0,
|
|
||||||
top: 0
|
|
||||||
}}
|
|
||||||
onClick={() => setOpen(true)} >
|
|
||||||
<MenuIcon />
|
|
||||||
</IconButton>
|
|
||||||
<SwipeableDrawer
|
<SwipeableDrawer
|
||||||
hideBackdrop={true}
|
|
||||||
disableBackdropTransition={true}
|
|
||||||
anchor="right"
|
anchor="right"
|
||||||
open={open}
|
hidden={hidden}
|
||||||
onClose={() => setOpen(false)}
|
className="sidebar">
|
||||||
onOpen={() => setOpen(true)}>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setOpen(false)} >
|
|
||||||
<CloseIcon />
|
|
||||||
</IconButton>
|
|
||||||
<ul>
|
<ul>
|
||||||
{verbs.map( (name) => (
|
{verbs.map( (name) => (
|
||||||
<li key={name}>
|
<li key={name}>
|
||||||
<Button onClick={() => {
|
<button onClick={() => {
|
||||||
console.log("clicked button for", name);
|
console.log("clicked button for", name);
|
||||||
sendWord(name);
|
sendWord(name);
|
||||||
}}>
|
}}>
|
||||||
{name}
|
{name}
|
||||||
</Button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
) )}
|
) )}
|
||||||
{children}
|
{children}
|
||||||
|
@ -44,7 +44,7 @@ export default function GraphRender() {
|
|||||||
else if (error) return `Error encountered: ${error}`;
|
else if (error) return `Error encountered: ${error}`;
|
||||||
else return (
|
else return (
|
||||||
<SigmaContainer
|
<SigmaContainer
|
||||||
allowInvalidContainer
|
settings={{allowInvalidContainer: true}}
|
||||||
graph={data}
|
graph={data}
|
||||||
style={{height: "400px", width: "100%", background: 'rgba(0,0,0,0)'}}
|
style={{height: "400px", width: "100%", background: 'rgba(0,0,0,0)'}}
|
||||||
/>
|
/>
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-section {
|
.landing-section {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@ -24,3 +25,12 @@ ol li {
|
|||||||
list-style-type: circle;
|
list-style-type: circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-height: 700px) {
|
||||||
|
.landing-column {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-container {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
@ -35,13 +35,7 @@ function Landing() {
|
|||||||
<hr/>
|
<hr/>
|
||||||
</header>
|
</header>
|
||||||
<div className="landing-container">
|
<div className="landing-container">
|
||||||
<section className="landing-section">
|
<section className="landing-section" style={{gridColumn: "3", gridRow: "1"}}>
|
||||||
<GraphRender />
|
|
||||||
</section>
|
|
||||||
<section className="landing-section">
|
|
||||||
<PageList />
|
|
||||||
</section>
|
|
||||||
<section className="landing-section">
|
|
||||||
{ loggedIn
|
{ loggedIn
|
||||||
?
|
?
|
||||||
<>
|
<>
|
||||||
@ -58,6 +52,12 @@ function Landing() {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
<section className="landing-section" style={{gridColumn: "1", gridRow: "1"}}>
|
||||||
|
{(typeof WebGL2RenderingContext != 'undefined') && <GraphRender />}
|
||||||
|
</section>
|
||||||
|
<section className="landing-section" style={{gridColumn: "2", gridRow: "1"}}>
|
||||||
|
<PageList />
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -12,6 +12,7 @@ import './Pages.css';
|
|||||||
function GhostPage({editing, ...props}) {
|
function GhostPage({editing, ...props}) {
|
||||||
const { pagenumber, editid } = useParams();
|
const { pagenumber, editid } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const fetchPageQuery = useQuery({
|
const fetchPageQuery = useQuery({
|
||||||
@ -117,7 +118,6 @@ function GhostPage({editing, ...props}) {
|
|||||||
</button>)}
|
</button>)}
|
||||||
{!editing && !editid && (
|
{!editing && !editid && (
|
||||||
<button
|
<button
|
||||||
disabled={!loggedIn}
|
|
||||||
onClick={() => navigate(`/${pagenumber}/edit`)}>
|
onClick={() => navigate(`/${pagenumber}/edit`)}>
|
||||||
Edit Page
|
Edit Page
|
||||||
</button>)}
|
</button>)}
|
||||||
|
@ -5,13 +5,13 @@ function AttrList({list, title, onChange, children}) {
|
|||||||
if (!list || !list.map || typeof list.map != 'function')
|
if (!list || !list.map || typeof list.map != 'function')
|
||||||
return <section className="page-contents">
|
return <section className="page-contents">
|
||||||
<h4>{title}</h4>
|
<h4>{title}</h4>
|
||||||
value is not a list: {list}
|
value is not a list: {JSON.stringify(list)}
|
||||||
</section>;
|
</section>;
|
||||||
|
|
||||||
return <section className="page-contents">
|
return <section className="page-contents">
|
||||||
<h4>{title}</h4>
|
<h4>{title}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{list.map((objectNum) => <li key={objectNum}>{objectNum}</li>)}
|
{list.map((objectNum) => <li key={objectNum}>{JSON.stringify(objectNum)}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
|
|||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import viteTsconfigPaths from 'vite-tsconfig-paths'
|
import viteTsconfigPaths from 'vite-tsconfig-paths'
|
||||||
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// depending on your application, base can also be "/"
|
// depending on your application, base can also be "/"
|
||||||
base: '/',
|
base: '/',
|
||||||
@ -12,4 +13,9 @@ export default defineConfig({
|
|||||||
// this sets a default port to 3000
|
// this sets a default port to 3000
|
||||||
port: 3000,
|
port: 3000,
|
||||||
},
|
},
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
// other options...
|
||||||
|
}
|
||||||
})
|
})
|
@ -273,7 +273,7 @@ function setAttribute(obj, attributeName, value) {
|
|||||||
const attributeStore = pullAttribute.get(verifyObjectReference(obj));
|
const attributeStore = pullAttribute.get(verifyObjectReference(obj));
|
||||||
const contents = JSON.parse(attributeStore.contents);
|
const contents = JSON.parse(attributeStore.contents);
|
||||||
|
|
||||||
if (isEmptyObject(value) && isArray(contents[attributes]))
|
if (isEmptyObject(value) && isArray(contents[attributeName]))
|
||||||
contents[attributeName] = [];
|
contents[attributeName] = [];
|
||||||
else
|
else
|
||||||
contents[attributeName] = value;
|
contents[attributeName] = value;
|
||||||
@ -284,10 +284,12 @@ function setAttribute(obj, attributeName, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isArray(obj) {
|
function isArray(obj) {
|
||||||
return Object.prototype.toString.apply(value) === '[object Array]';
|
return Object.prototype.toString.apply(obj) === '[object Array]';
|
||||||
}
|
}
|
||||||
function isEmptyObject(obj) {
|
function isEmptyObject(obj) {
|
||||||
if (typeof obj !== "obj") return false;
|
if (typeof obj !== "object") return false;
|
||||||
|
|
||||||
|
if (isArray(obj)) return false;
|
||||||
|
|
||||||
for (const prop in obj) {
|
for (const prop in obj) {
|
||||||
if (Object.hasOwn(obj, prop)) return true;
|
if (Object.hasOwn(obj, prop)) return true;
|
||||||
|
517
server/package-lock.json
generated
517
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,8 @@
|
|||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --watch --env-file=.env server.js",
|
"start": "node --watch --env-file=.env server.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"ver": "node --version"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -15,7 +16,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argon2": "^0.41.1",
|
"argon2": "^0.41.1",
|
||||||
"better-sqlite3": "^11.3.0",
|
"better-sqlite3": "^11.5.0",
|
||||||
"better-sqlite3-session-store": "^0.1.0",
|
"better-sqlite3-session-store": "^0.1.0",
|
||||||
"client-sessions": "^0.8.0",
|
"client-sessions": "^0.8.0",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
@ -27,7 +28,7 @@
|
|||||||
"graphology-layout-forceatlas2": "^0.10.1",
|
"graphology-layout-forceatlas2": "^0.10.1",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^25.0.0",
|
||||||
"node": "^22.10.0",
|
"node": "^22.11.0",
|
||||||
"nodemon": "^3.1.5",
|
"nodemon": "^3.1.5",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"wasmoon": "^1.16.0"
|
"wasmoon": "^1.16.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user