summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSam Nystrom <sam@samnystrom.dev>2024-03-12 14:52:25 +0000
committerSam Nystrom <sam@samnystrom.dev>2024-03-12 17:34:56 -0400
commita9aa246f58dcb2664c4e7a1bd98e69c19e7d7000 (patch)
tree2795ec23ec748024dd863215a9735cb8c3825579 /src
parent367fe51e7e1c55e39299e2e667ca4f399a474019 (diff)
Add rudimentary backend and auth
Diffstat (limited to 'src')
-rw-r--r--src/NodeEditor.tsx36
-rw-r--r--src/index.tsx18
-rw-r--r--src/pages/Home.tsx8
-rw-r--r--src/pages/LogIn.tsx33
-rw-r--r--src/pages/ProjectsList.tsx49
-rw-r--r--src/pages/SignUp.tsx53
-rw-r--r--src/pages/index.ts4
-rw-r--r--src/pb.ts5
8 files changed, 189 insertions, 17 deletions
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