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": {
|
||||
"start": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"test": "vitest"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@ -53,5 +54,9 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"jsdom": "^25.0.1",
|
||||
"vitest": "^2.1.5"
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ a:visited {
|
||||
max-width: 76ch;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 3rem;
|
||||
color: white;
|
||||
}
|
||||
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;
|
||||
background: lightgreen;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
bottom: 1rem;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
.commandline form {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.commandline input {
|
||||
flex: 10;
|
||||
flex: 1;
|
||||
font-size: 16pt;
|
||||
border: none;
|
||||
padding: 2rem;
|
||||
border-radius: 1rem;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.commandline button {
|
||||
border-radius: 4rem;
|
||||
width: 4rem;
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { Fab } from '@mui/material';
|
||||
|
||||
import './CommandEntry.css';
|
||||
|
||||
const CommandEntry = forwardRef(({ setCommand, onSubmitCommand, command }, ref) => {
|
||||
return (
|
||||
<div className="commandline" >
|
||||
<div className="commandline" ref={ref} >
|
||||
<input
|
||||
ref={ref}
|
||||
id="command_input"
|
||||
onChange={(e) => setCommand(e.target.value)}
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder="Enter a command!"
|
||||
value={command} />
|
||||
<Fab onClick={ () => { console.log("clicked"); onSubmitCommand(command); }} >
|
||||
<button
|
||||
onClick={ () => onSubmitCommand(command) } >
|
||||
Send
|
||||
</Fab>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -11,6 +11,7 @@ import { JsonEditor } from 'json-edit-react';
|
||||
import MessageFeed from './MessageFeed.jsx';
|
||||
import WordSidebar from './WordSidebar.jsx';
|
||||
import CommandEntry from './CommandEntry.jsx';
|
||||
import Shim from '../components/Shim.jsx';
|
||||
|
||||
function Live({...props}) {
|
||||
const navigate = useNavigate();
|
||||
@ -22,7 +23,6 @@ function Live({...props}) {
|
||||
const [ editing, setEditing ] = useState(false);
|
||||
const [ connecting, setConnecting ] = useState(true);
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const commandEntryRef = useRef(null);
|
||||
|
||||
//setting up the websocket and using its data!
|
||||
@ -61,6 +61,8 @@ function Live({...props}) {
|
||||
setType(fetchPageQuery.data?.type == 1);
|
||||
}, [fetchPageQuery.data?.type]);
|
||||
|
||||
const editorRef = useRef(null);
|
||||
|
||||
// extended attribute store!
|
||||
const fetchAttributesQuery = useQuery({
|
||||
queryKey: ['attributes', currentNumber],
|
||||
@ -72,7 +74,6 @@ function Live({...props}) {
|
||||
setAttributes(fetchAttributesQuery.data);
|
||||
}, [fetchAttributesQuery.data]);
|
||||
|
||||
console.log("is live reloading wrking");
|
||||
// verbs available to us
|
||||
const fetchVerbsQuery = useQuery({
|
||||
queryKey: ['my verbs'],
|
||||
@ -169,23 +170,27 @@ function Live({...props}) {
|
||||
<ExtendedAttributes
|
||||
attributes={attributes} />
|
||||
}
|
||||
<Shim height={true} width={false} duplicateOf={commandEntryRef}/>
|
||||
</div>
|
||||
<MessageFeed messages={messageHistory}>
|
||||
<button disabled={connecting} onClick={() => setConnecting(true)}>
|
||||
<p>
|
||||
{{
|
||||
[ReadyState.CONNECTING]: 'Connecting',
|
||||
[ReadyState.OPEN]: 'Open',
|
||||
[ReadyState.CLOSING]: 'Closing',
|
||||
[ReadyState.CLOSED]: 'Closed',
|
||||
[ReadyState.UNINSTANTIATED]: 'Uninstantiated',
|
||||
}[readyState]}</button>
|
||||
}[readyState]}
|
||||
</p>
|
||||
<button onClick={()=> setMessageHistory([])}>Clear History</button>
|
||||
</MessageFeed>
|
||||
<WordSidebar verbs={verbs} sendWord={(word) => {
|
||||
setCommand((command + " " + word).trim());
|
||||
commandEntryRef.current.focus();
|
||||
// maybe set focus to the command entry?
|
||||
}}>
|
||||
<WordSidebar
|
||||
verbs={verbs}
|
||||
sendWord={(word) => {
|
||||
setCommand((command + " " + word).trim());
|
||||
// diong this outside of react so shim can... ugh
|
||||
document.getElementById("command_input").focus();
|
||||
}}>
|
||||
</WordSidebar>
|
||||
<CommandEntry
|
||||
ref={commandEntryRef}
|
||||
|
@ -1,28 +1,19 @@
|
||||
.messages-tray {
|
||||
display: none;/*flex;*/
|
||||
display: flex;/*flex;*/
|
||||
flex-direction: column;
|
||||
transition: all 0.1s linear;
|
||||
margin: 0;
|
||||
background: lightgreen;
|
||||
position: fixed;
|
||||
width: 30ch;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.messages-hidden {
|
||||
transform: translateX(-20ch);
|
||||
}
|
||||
|
||||
.messages-tray ol {
|
||||
flex: 10;
|
||||
padding-left: 1ch;
|
||||
overflow: auto;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.messages-tray li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.messages-tray li:nth-child(even) {
|
||||
background: lightgray;
|
||||
}
|
@ -1,45 +1,21 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { useForm } from "react-hook-form";
|
||||
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 { useEffect, useRef } from 'react';
|
||||
import SwipeableDrawer from '../components/SwipeableDrawer.jsx';
|
||||
|
||||
import './MessageFeed.css';
|
||||
|
||||
function MessageFeed({messages=[], children}) {
|
||||
const [ open, setOpen ] = useState(false);
|
||||
function MessageFeed({messages=[], hidden=true, children}) {
|
||||
const listContainer = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (listContainer.current)
|
||||
listContainer.current.scrollTo(0, listContainer.current.scrollHeight);
|
||||
}, [listContainer, messages])
|
||||
}, [listContainer.current, messages])
|
||||
|
||||
return (<>
|
||||
<IconButton
|
||||
size="large"
|
||||
style={{
|
||||
visible: open,
|
||||
position: "fixed",
|
||||
padding: "2rem",
|
||||
left: 0,
|
||||
top: 0
|
||||
}}
|
||||
onClick={() => setOpen(true)} >
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
return (
|
||||
<SwipeableDrawer
|
||||
anchor="left"
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOpen={() => setOpen(true)}>
|
||||
<IconButton
|
||||
variant="text"
|
||||
onClick={() => setOpen(false)} >
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
hidden={hidden}
|
||||
className="messages-tray">
|
||||
<h2>Message history:</h2>
|
||||
<ol ref={listContainer}>
|
||||
{messages.map((message, idx) =>
|
||||
@ -50,7 +26,7 @@ function MessageFeed({messages=[], children}) {
|
||||
</ol>
|
||||
{children}
|
||||
</SwipeableDrawer>
|
||||
</>);
|
||||
);
|
||||
}
|
||||
|
||||
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 { useQuery } from '@tanstack/react-query';
|
||||
import { useLoggedIn } from '../AuthProvider.jsx';
|
||||
import { fetchCurrentVerbs } from '../apiTools.jsx';
|
||||
import { Button, IconButton, SwipeableDrawer } from '@mui/material';
|
||||
import SwipeableDrawer from '../components/SwipeableDrawer.jsx';
|
||||
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
import './Sidebar.css';
|
||||
import './WordSidebar.css';
|
||||
|
||||
function WordSidebar({children, verbs, hidden=true, sendWord=(()=>null)}) {
|
||||
const [open, setOpen] = useState(!hidden);
|
||||
const loggedIn = useLoggedIn();
|
||||
|
||||
return (<>
|
||||
<IconButton
|
||||
size="large"
|
||||
variant="contained"
|
||||
style={{
|
||||
visible: open,
|
||||
position: "fixed",
|
||||
padding: "2rem",
|
||||
right: 0,
|
||||
top: 0
|
||||
}}
|
||||
onClick={() => setOpen(true)} >
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<SwipeableDrawer
|
||||
hideBackdrop={true}
|
||||
disableBackdropTransition={true}
|
||||
anchor="right"
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOpen={() => setOpen(true)}>
|
||||
<IconButton
|
||||
onClick={() => setOpen(false)} >
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
hidden={hidden}
|
||||
className="sidebar">
|
||||
<ul>
|
||||
{verbs.map( (name) => (
|
||||
<li key={name}>
|
||||
<Button onClick={() => {
|
||||
<button onClick={() => {
|
||||
console.log("clicked button for", name);
|
||||
sendWord(name);
|
||||
}}>
|
||||
{name}
|
||||
</Button>
|
||||
</button>
|
||||
</li>
|
||||
) )}
|
||||
{children}
|
||||
|
@ -44,7 +44,7 @@ export default function GraphRender() {
|
||||
else if (error) return `Error encountered: ${error}`;
|
||||
else return (
|
||||
<SigmaContainer
|
||||
allowInvalidContainer
|
||||
settings={{allowInvalidContainer: true}}
|
||||
graph={data}
|
||||
style={{height: "400px", width: "100%", background: 'rgba(0,0,0,0)'}}
|
||||
/>
|
||||
|
@ -12,6 +12,7 @@
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.landing-section {
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
@ -24,3 +25,12 @@ ol li {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
@media (max-height: 700px) {
|
||||
.landing-column {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.landing-container {
|
||||
display: block;
|
||||
}
|
||||
}
|
@ -35,13 +35,7 @@ function Landing() {
|
||||
<hr/>
|
||||
</header>
|
||||
<div className="landing-container">
|
||||
<section className="landing-section">
|
||||
<GraphRender />
|
||||
</section>
|
||||
<section className="landing-section">
|
||||
<PageList />
|
||||
</section>
|
||||
<section className="landing-section">
|
||||
<section className="landing-section" style={{gridColumn: "3", gridRow: "1"}}>
|
||||
{ loggedIn
|
||||
?
|
||||
<>
|
||||
@ -58,6 +52,12 @@ function Landing() {
|
||||
</>
|
||||
}
|
||||
</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>
|
||||
);
|
||||
|
@ -12,6 +12,7 @@ import './Pages.css';
|
||||
function GhostPage({editing, ...props}) {
|
||||
const { pagenumber, editid } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const fetchPageQuery = useQuery({
|
||||
@ -117,7 +118,6 @@ function GhostPage({editing, ...props}) {
|
||||
</button>)}
|
||||
{!editing && !editid && (
|
||||
<button
|
||||
disabled={!loggedIn}
|
||||
onClick={() => navigate(`/${pagenumber}/edit`)}>
|
||||
Edit Page
|
||||
</button>)}
|
||||
|
@ -5,13 +5,13 @@ function AttrList({list, title, onChange, children}) {
|
||||
if (!list || !list.map || typeof list.map != 'function')
|
||||
return <section className="page-contents">
|
||||
<h4>{title}</h4>
|
||||
value is not a list: {list}
|
||||
value is not a list: {JSON.stringify(list)}
|
||||
</section>;
|
||||
|
||||
return <section className="page-contents">
|
||||
<h4>{title}</h4>
|
||||
<ul>
|
||||
{list.map((objectNum) => <li key={objectNum}>{objectNum}</li>)}
|
||||
{list.map((objectNum) => <li key={objectNum}>{JSON.stringify(objectNum)}</li>)}
|
||||
</ul>
|
||||
</section>
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import viteTsconfigPaths from 'vite-tsconfig-paths'
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
// depending on your application, base can also be "/"
|
||||
base: '/',
|
||||
@ -12,4 +13,9 @@ export default defineConfig({
|
||||
// this sets a default port to 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 contents = JSON.parse(attributeStore.contents);
|
||||
|
||||
if (isEmptyObject(value) && isArray(contents[attributes]))
|
||||
if (isEmptyObject(value) && isArray(contents[attributeName]))
|
||||
contents[attributeName] = [];
|
||||
else
|
||||
contents[attributeName] = value;
|
||||
@ -284,10 +284,12 @@ function setAttribute(obj, attributeName, value) {
|
||||
}
|
||||
|
||||
function isArray(obj) {
|
||||
return Object.prototype.toString.apply(value) === '[object Array]';
|
||||
return Object.prototype.toString.apply(obj) === '[object Array]';
|
||||
}
|
||||
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) {
|
||||
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",
|
||||
"scripts": {
|
||||
"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": {
|
||||
"type": "git",
|
||||
@ -15,7 +16,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"argon2": "^0.41.1",
|
||||
"better-sqlite3": "^11.3.0",
|
||||
"better-sqlite3": "^11.5.0",
|
||||
"better-sqlite3-session-store": "^0.1.0",
|
||||
"client-sessions": "^0.8.0",
|
||||
"express": "^4.21.0",
|
||||
@ -27,7 +28,7 @@
|
||||
"graphology-layout-forceatlas2": "^0.10.1",
|
||||
"highlight.js": "^11.10.0",
|
||||
"jsdom": "^25.0.0",
|
||||
"node": "^22.10.0",
|
||||
"node": "^22.11.0",
|
||||
"nodemon": "^3.1.5",
|
||||
"showdown": "^2.1.0",
|
||||
"wasmoon": "^1.16.0"
|
||||
|
Loading…
Reference in New Issue
Block a user