diff options
| author | Sam Nystrom <sam@samnystrom.dev> | 2024-03-16 21:09:02 +0000 |
|---|---|---|
| committer | Sam Nystrom <sam@samnystrom.dev> | 2024-03-16 17:16:58 -0400 |
| commit | 3b8c7e391e1f370af74df30a14a699a49543f918 (patch) | |
| tree | c2f0514911963843def2267329e14554a4d21fe9 /src | |
| parent | 13b10dd150f9c9cf382fae9802a704ba9c582a62 (diff) | |
refactor: extract NodeEditor into a component
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/NodeEditor.css (renamed from src/NodeEditor.css) | 0 | ||||
| -rw-r--r-- | src/components/NodeEditor.tsx (renamed from src/NodeEditor.tsx) | 80 | ||||
| -rw-r--r-- | src/components/index.ts | 4 | ||||
| -rw-r--r-- | src/index.tsx | 5 | ||||
| -rw-r--r-- | src/pages/Editor.tsx | 30 | ||||
| -rw-r--r-- | src/pages/index.ts | 5 |
6 files changed, 78 insertions, 46 deletions
diff --git a/src/NodeEditor.css b/src/components/NodeEditor.css index 5a8a683..5a8a683 100644 --- a/src/NodeEditor.css +++ b/src/components/NodeEditor.css diff --git a/src/NodeEditor.tsx b/src/components/NodeEditor.tsx index 781e64b..758fb76 100644 --- a/src/NodeEditor.tsx +++ b/src/components/NodeEditor.tsx @@ -1,11 +1,14 @@ import { useContext, useEffect, useMemo, useCallback, useRef } from 'preact/hooks'; import { signal, computed, batch, useSignal, useComputed, Signal } from '@preact/signals'; -import { Pb } from './context.ts'; -import { NodeComponent, SocketHandlers } from './node.ts'; -import { nodeRegistry } from './nodes'; -import type { SocketHandler, NodeInfo } from './node.ts'; -import { InputSocket } from './dataflow.ts'; -import { Toolbar, ButtonMenu, MenuItem } from './components'; +import { Pb } from '../context.ts'; +import type { Project } from '../types.ts'; +import { NodeComponent, SocketHandlers } from '../node.ts'; +import { nodeRegistry } from '../nodes'; +import type { SocketHandler, NodeInfo } from '../node.ts'; +import { InputSocket } from '../dataflow.ts'; +import Toolbar from './Toolbar.tsx'; +import ButtonMenu from './ButtonMenu.tsx'; +import MenuItem from './MenuItem.tsx'; import './NodeEditor.css'; interface NodeInstance { @@ -17,22 +20,19 @@ interface NodeInstance { outputs: Record<string, Signal<any>>; } -const nodeFactory = () => { - let nextNodeId = 0; - return (x: number, y: number, { component, func, inputs }: NodeInfo<any, any>): NodeInstance => { - const mapEntries = (obj: {}, f: (x: [string, any]) => [string, any]) => ( - Object.fromEntries(Object.entries(obj).map(f)) - ); - const instanceInputs = mapEntries(inputs, ([k, v]) => [k, new InputSocket(v)]); - const output = computed(() => func(mapEntries(instanceInputs, ([k, v]) => [k, v.value]))); - return { - id: nextNodeId++, - component, - x: signal(x), - y: signal(y), - inputs: instanceInputs, - outputs: mapEntries(output.value, ([k, _]) => [k, computed(() => output.value[k])]), - }; +const instantiateNode = (id: string, x: number, y: number, { component, func, inputs }: NodeInfo<any, any>): NodeInstance => { + const mapEntries = (obj: {}, f: (x: [string, any]) => [string, any]) => ( + Object.fromEntries(Object.entries(obj).map(f)) + ); + const instanceInputs = mapEntries(inputs, ([k, v]) => [k, new InputSocket(v)]); + const output = computed(() => func(mapEntries(instanceInputs, ([k, v]) => [k, v.value]))); + return { + id, + component, + x: signal(x), + y: signal(y), + inputs: instanceInputs, + outputs: mapEntries(output.value, ([k, _]) => [k, computed(() => output.value[k])]), }; }; @@ -64,18 +64,16 @@ interface LinkData extends LinkProps { } export interface NodeEditorProps { - user: string; - project: string; + project: Project; } -const NodeEditor = ({ user, project }: NodeEditorProps) => { +const NodeEditor = ({ project }: NodeEditorProps) => { const pb = useContext(Pb)!; const offsetX = useSignal(0); const offsetY = useSignal(0); const scale = useSignal(1); - const instantiateNode = useMemo(nodeFactory, []); const svgRef = useRef<SVGSVGElement | null>(null); const nodes = useSignal<NodeInstance[]>([]); @@ -84,14 +82,14 @@ const NodeEditor = ({ user, project }: NodeEditorProps) => { 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 }); - const instances = projectNodes.map(node => instantiateNode(node.x, node.y, node.name)); - nodes.value = nodes.value.concat(instances); + useEffect(() => { + (async () => { + const filter = pb.filter('project.id = {:id}', { id: project.id }); + const projectNodes = await pb.collection('nodes').getFullList({ filter }); + const projectLinks = await pb.collection('links').getFullList({ filter }); + const instances = projectNodes.map(node => instantiateNode(node.id, node.x, node.y, node.name)); + nodes.value = nodes.value.concat(instances); + })(); }, []); const onOutMouseDown: SocketHandler = useCallback((nodeId, socket, event) => { @@ -200,11 +198,11 @@ const NodeEditor = ({ user, project }: NodeEditorProps) => { }); }, []); - const socketHandlers = { + const socketHandlers = useMemo(() => ({ onOutMouseDown, onInMouseDown, onInMouseUp, - }; + }), []); const onKeyDown = useCallback((event: KeyboardEvent) => { if (event.code === 'KeyX') { @@ -239,16 +237,18 @@ const NodeEditor = ({ user, project }: NodeEditorProps) => { scale.value *= 1 + delta; }), []); - const addNode = useCallback((node: NodeInfo<any, any>) => { - nodes.value = nodes.value.concat(instantiateNode(100, 100, node)); + const addNode = useCallback(async (name: string, info: NodeInfo<any, any>) => { + const node = await pb.collection('nodes').create({ x: 100, y: 100, type: name, project: projectId, collapsed: false }); + alert(JSON.stringify(node)); + nodes.value = nodes.value.concat(instantiateNode(node.id, node.x, node.y, info)); }, []); return ( <div class="__NodeEditor"> - <Toolbar title={project}> + <Toolbar title={project.name}> <ButtonMenu label="Add"> {Object.entries(nodeRegistry).map(([name, node]) => ( - <MenuItem label={name} onClick={() => addNode(node)} /> + <MenuItem label={name} onClick={e => addNode(name, node)} /> ))} </ButtonMenu> </Toolbar> diff --git a/src/components/index.ts b/src/components/index.ts index badd32e..21b1267 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,6 +8,7 @@ export { default as FormLabel } from './FormLabel.tsx'; export { default as Header } from './Header.tsx'; export { default as Menu } from './Menu.tsx'; export { default as MenuItem } from './MenuItem.tsx'; +export { default as NodeEditor } from './NodeEditor.tsx'; export { default as TextInput } from './TextInput.tsx'; export { default as Toolbar } from './Toolbar.tsx'; @@ -19,7 +20,8 @@ export type { ContentProps } from './Content.tsx'; export type { FormLabelProps } from './FormLabel.tsx'; export type { FormProps } from './Form.tsx'; export type { HeaderProps } from './Header.tsx'; -export type { MenuItemProps } from './MenuItem.tsx'; export type { MenuProps } from './Menu.tsx'; +export type { MenuItemProps } from './MenuItem.tsx'; +export type { NodeEditorProps } from './NodeEditor.tsx'; export type { TextInputProps } from './TextInput.tsx'; export type { ToolbarProps } from './Toolbar.tsx'; diff --git a/src/index.tsx b/src/index.tsx index 6fdf07e..cd8c870 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,8 +3,7 @@ import { useMemo } from 'preact/hooks'; import { Router } from 'preact-router'; import PocketBase from 'pocketbase'; import { Pb } from './context.ts'; -import { Home, SignUp, LogIn, ProjectsList } from './pages'; -import NodeEditor from './NodeEditor.tsx'; +import { Home, SignUp, LogIn, ProjectsList, Editor } from './pages'; import './index.css'; export const App = () => { @@ -16,7 +15,7 @@ export const App = () => { <SignUp path="/signup" /> <LogIn path="/login" /> <ProjectsList path="/:user" /> - <NodeEditor path="/:user/:project" /> + <Editor path="/:user/:project" /> </Router> </Pb.Provider> ); diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx new file mode 100644 index 0000000..c929595 --- /dev/null +++ b/src/pages/Editor.tsx @@ -0,0 +1,30 @@ +import { useEffect, useMemo, useContext } from 'preact/hooks'; +import { useSignal } from '@preact/signals'; +import { Pb } from '../context.ts'; +import type { Project } from '../types.ts'; +import { NodeEditor } from '../components'; + +export interface EditorProps { + user: string; + project: string; +} + +const Editor = ({ user, project }: EditorProps) => { + const pb = useContext(Pb)!; + const projectData = useSignal<Project | null>(null); + + useEffect(() => { + (async () => { + projectData.value = await pb.collection('projects') + .getFirstListItem(pb.filter('owner.username = {:user} && name = {:project}', { user, project })); + })(); + }, []); + + return ( + <> + {!!projectData.value && <NodeEditor project={projectData.value} />} + </> + ); +}; + +export default Editor; diff --git a/src/pages/index.ts b/src/pages/index.ts index bd162cf..27e3906 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -1,4 +1,5 @@ +export { default as Editor } from './Editor.tsx'; export { default as Home } from './Home.tsx'; -export { default as SignUp } from './SignUp.tsx'; export { default as LogIn } from './LogIn.tsx'; -export { default as ProjectsList } from './ProjectsList.tsx';
\ No newline at end of file +export { default as ProjectsList } from './ProjectsList.tsx'; +export { default as SignUp } from './SignUp.tsx'; |
