diff options
| -rw-r--r-- | package.json | 12 | ||||
| -rw-r--r-- | src/NodeEditor.tsx | 36 | ||||
| -rw-r--r-- | src/index.tsx | 18 | ||||
| -rw-r--r-- | src/pages/Home.tsx | 8 | ||||
| -rw-r--r-- | src/pages/LogIn.tsx | 33 | ||||
| -rw-r--r-- | src/pages/ProjectsList.tsx | 49 | ||||
| -rw-r--r-- | src/pages/SignUp.tsx | 53 | ||||
| -rw-r--r-- | src/pages/index.ts | 4 | ||||
| -rw-r--r-- | src/pb.ts | 5 |
9 files changed, 196 insertions, 22 deletions
diff --git a/package.json b/package.json index f7ec3c5..9ac9b76 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,16 @@ "type": "module", "scripts": { "check": "tsc", - "dev": "esbuild src/index.tsx --loader:.svg=dataurl --loader:.wasm=file --bundle --sourcemap --format=esm --outdir=public --watch --servedir=public", - "asbuild:debug": "asc assembly/index.ts --target debug", - "asbuild:release": "asc assembly/index.ts --target release", - "build": "rm -rf dist && cp -r public dist && bun run asbuild:release && esbuild src/index.tsx --loader:.svg=dataurl --loader:.wasm=file --bundle --minify --format=esm --outdir=dist" + "dev": "esbuild src/index.tsx --loader:.svg=dataurl --loader:.wasm=file --bundle --sourcemap --format=esm --outdir=public --watch --servedir=public --serve-fallback=public/index.html", + "asbuild:debug": "asc assembly/index.ts --target debug", + "asbuild:release": "asc assembly/index.ts --target release", + "build": "rm -rf dist && cp -r public dist && esbuild src/index.tsx --loader:.svg=dataurl --loader:.wasm=file --bundle --minify --format=esm --outdir=dist" }, "dependencies": { "@preact/signals": "^1.2.2", - "preact": "^10.19.6" + "pocketbase": "^0.21.1", + "preact": "^10.19.6", + "preact-router": "^4.1.2" }, "devDependencies": { "assemblyscript": "^0.27.24", diff --git a/src/NodeEditor.tsx b/src/NodeEditor.tsx index e4b584d..3a80825 100644 --- a/src/NodeEditor.tsx +++ b/src/NodeEditor.tsx @@ -1,5 +1,6 @@ -import { useEffect, useMemo, useRef } from 'preact/hooks'; +import { useContext, useEffect, useMemo, useRef } from 'preact/hooks'; import { signal, computed, batch, useSignal, useComputed, Signal } from '@preact/signals'; +import { Pb } from './pb.ts'; import { nodeRegistry } from './nodes'; import { SocketHandlers, SocketHandler, NodeInfo } from './node.tsx'; import { InputSocket } from './dataflow.ts'; @@ -55,7 +56,9 @@ interface LinkData extends LinkProps { to: { nodeId: number, socket: string }; } -export const NodeEditor = () => { +export const NodeEditor = ({ user, project }) => { + const pb = useContext(Pb); + const offsetX = useSignal(0); const offsetY = useSignal(0); const scale = useSignal(1); @@ -63,21 +66,24 @@ export const NodeEditor = () => { const instantiateNode = useMemo(nodeFactory, []); const svgRef = useRef(null); - const initialNodes = useMemo(() => [ - instantiateNode(100, 100, nodeRegistry['Linspace']), - instantiateNode(350, 200, nodeRegistry['Math']), - instantiateNode(350, 50, nodeRegistry['Intersperse']), - instantiateNode(600, 100, nodeRegistry['Fourier Transform']), - instantiateNode(900, 100, nodeRegistry['Viewer']), - instantiateNode(900, 250, nodeRegistry['Plot']), - ]); - - const nodes = useSignal(initialNodes); + const nodes = useSignal([]); const currentLink = useSignal<null | Omit<LinkData, 'to'>>(null); const links = useSignal<LinkData[]>([]); const allLinks = useComputed(() => (links.value as LinkProps[]).concat(currentLink.value as LinkProps ?? [])); + useEffect(async () => { + const projectData = await pb.collection('projects') + .getFirstListItem(pb.filter('name = {:project} && owner.username = {:user}', { project, user })); + const filter = pb.filter('project.id = {:id}', { id: projectData.id }); + const projectNodes = await pb.collection('nodes').getFullList({ filter }); + const projectLinks = await pb.collection('links').getFullList({ filter }); + console.log(projectNodes); + console.log(projectLinks); + const instances = projectNodes.map(node => instantiateNode(node.x, node.y, node.name)); + nodes.value = nodes.value.concat(instances); + }, []); + const onOutMouseDown: SocketHandler = (nodeId, socket, event) => { event.stopPropagation(); const svgRect = svgRef.current?.getBoundingClientRect(); @@ -87,7 +93,7 @@ export const NodeEditor = () => { pos.x -= svgX; pos.y -= svgY; const node = nodes.value.find(x => x.id === nodeId); - if (!node) throw new Error(); + if (!node) throw new Error('no node for mousedown id'); const xOffs = (pos.x - offsetX.value) / scale.value - node.x.value; const yOffs = (pos.y - offsetY.value) / scale.value - node.y.value; @@ -122,7 +128,7 @@ export const NodeEditor = () => { const i = links.value.findIndex(l => l.to.nodeId === nodeId && l.to.socket === socket); if (i == -1) return; const node = nodes.value.find(x => x.id === nodeId); - if (!node) throw new Error(); + if (!node) throw new Error('no node for inmousedown id'); const svgRect = svgRef.current?.getBoundingClientRect(); const svgX = svgRect?.x ?? 0; @@ -159,7 +165,7 @@ export const NodeEditor = () => { event.stopPropagation(); const fromNode = nodes.value.find(x => x.id === currentLink.value!.from.nodeId); const node = nodes.value.find(x => x.id === nodeId); - if (!node || !fromNode) throw new Error(); + if (!node || !fromNode) throw new Error('no nodes for inmouseup ids'); const svgRect = svgRef.current?.getBoundingClientRect(); const svgX = svgRect?.x ?? 0; diff --git a/src/index.tsx b/src/index.tsx index bff0b70..610fb70 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,11 +1,25 @@ import { render } from 'preact'; +import { useMemo } from 'preact/hooks'; +import { Router } from 'preact-router'; +import PocketBase from 'pocketbase'; +import { Pb } from './pb.ts'; +import { Home, SignUp, LogIn, ProjectsList } from './pages'; import { NodeEditor } from './NodeEditor.tsx'; import './index.css'; export const App = () => { + const pb = useMemo(() => new PocketBase(`https://${window.location.hostname}:8080/`)); return ( - <NodeEditor /> + <Pb.Provider value={pb}> + <Router> + <Home path="/" /> + <SignUp path="/signup" /> + <LogIn path="/login" /> + <ProjectsList path="/:user" /> + <NodeEditor path="/:user/:project" /> + </Router> + </Pb.Provider> ); }; -render(<App />, document.body); +render(<App />, document.body);
\ No newline at end of file diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx new file mode 100644 index 0000000..7762636 --- /dev/null +++ b/src/pages/Home.tsx @@ -0,0 +1,8 @@ +export const Home = () => { + return ( + <header> + <a href="/signup">Sign Up</a> + <a href="/login">Log In</a> + </header> + ); +};
\ No newline at end of file diff --git a/src/pages/LogIn.tsx b/src/pages/LogIn.tsx new file mode 100644 index 0000000..c787dce --- /dev/null +++ b/src/pages/LogIn.tsx @@ -0,0 +1,33 @@ +import { useContext } from 'preact/hooks'; +import { useSignal } from '@preact/signals'; +import { route } from 'preact-router'; +import { Pb } from '../pb.ts'; + +export const LogIn = () => { + const pb = useContext(Pb); + + const email = useSignal(''); + const password = useSignal(''); + + const onSubmit = async (event: SubmitEvent) => { + event.preventDefault(); + const user = await pb.collection('users').authWithPassword(email.value, password.value); + if (pb.authStore.isValid) { + route('/' + user.username); + } + }; + + return ( + <form onSubmit={onSubmit}> + <label> + Email: + <input type="text" value={email} onInput={e => email.value = e.target.value} /> + </label> + <label> + Password: + <input type="password" value={password} onInput={e => password.value = e.target.value} /> + </label> + <button>Log In</button> + </form> + ); +};
\ No newline at end of file diff --git a/src/pages/ProjectsList.tsx b/src/pages/ProjectsList.tsx new file mode 100644 index 0000000..b30c73e --- /dev/null +++ b/src/pages/ProjectsList.tsx @@ -0,0 +1,49 @@ +import { useContext, useEffect } from 'preact/hooks'; +import { useSignal } from '@preact/signals'; +import { route } from 'preact-router'; +import { Pb } from '../pb.ts'; + +export const ProjectsList = ({ user }) => { + console.log(user); + const pb = useContext(Pb); + const projects = useSignal(null); + const projectName = useSignal(''); + + useEffect(() => { + pb.collection('projects') + .getList(1, 20, { sort: '-mtime' }) + .then(p => projects.value = p); + }, []); + + const onCreateProject = async (event: FormEvent) => { + event.preventDefault(); + const project = await pb.collection('projects').create({ + name: projectName.value, + owner: pb.authStore.model.id, + }); + route(`/${user}/${project.name}`); + }; + + if (projects.value === null) { + return ( + <p>Loading...</p> + ); + } + return ( + <main> + <h1>{user}</h1> + <form onSubmit={onCreateProject}> + <label> + Name: + <input type="text" value={projectName} onInput={e => projectName.value = e.target.value} /> + </label> + <button>Create roject</button> + </form> + <ul> + {projects.value.items.map(p => ( + <li><a href={`/${user}/${p.name}`}>{p.name}</a></li> + ))} + </ul> + </main> + ); +};
\ No newline at end of file diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx new file mode 100644 index 0000000..99cc7d6 --- /dev/null +++ b/src/pages/SignUp.tsx @@ -0,0 +1,53 @@ +import { useContext } from 'preact/hooks'; +import { useSignal } from '@preact/signals'; +import { route } from 'preact-router'; +import { Pb } from '../pb.ts'; + +export const SignUp = () => { + const pb = useContext(Pb); + + const username = useSignal(''); + const email = useSignal(''); + const password = useSignal(''); + const confirm = useSignal(''); + + const onSubmit = async (event: SubmitEvent) => { + event.preventDefault(); + const user = await pb.collection('users').create({ + username: username.value, + email: email.value, + emailVisibility: true, + password: password.value, + passwordConfirm: confirm.value, + }); + if (pb.authStore.isValid) { + route('/' + user.username); + } + }; + + return ( + <main class="container"> + <article> + <form onSubmit={onSubmit}> + <label> + Username: + <input type="text" value={username} onInput={e => username.value = e.target.value} /> + </label> + <label> + Email: + <input type="text" value={email} onInput={e => email.value = e.target.value} /> + </label> + <label> + Password: + <input type="password" value={password} onInput={e => password.value = e.target.value} /> + </label> + <label> + Confirm Password: + <input type="password" value={confirm} onInput={e => confirm.value = e.target.value} /> + </label> + <button>Sign Up</button> + </form> + </article> + </main> + ); +};
\ No newline at end of file diff --git a/src/pages/index.ts b/src/pages/index.ts new file mode 100644 index 0000000..8b28ea6 --- /dev/null +++ b/src/pages/index.ts @@ -0,0 +1,4 @@ +export { Home } from './Home.tsx'; +export { SignUp } from './SignUp.tsx'; +export { LogIn } from './LogIn.tsx'; +export { ProjectsList } from './ProjectsList.tsx';
\ No newline at end of file diff --git a/src/pb.ts b/src/pb.ts new file mode 100644 index 0000000..2e77787 --- /dev/null +++ b/src/pb.ts @@ -0,0 +1,5 @@ +import { createContext } from 'preact'; + +const Pb = createContext(); + +export { Pb };
\ No newline at end of file |
