summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSam Nystrom <sam@samnystrom.dev>2024-03-13 22:37:58 +0000
committerSam Nystrom <sam@samnystrom.dev>2024-03-13 20:17:07 -0400
commitb4d7b00dd1add8e1cffb7771539fa6c419b64846 (patch)
treea634501f724077d981e7c6121f9960cfe24a22d4 /src
parent9eb1625ec5de3c221ed0445dde874fcb1dc3ff48 (diff)
refactor: extract nodes/sockets to components
Diffstat (limited to 'src')
-rw-r--r--src/NodeEditor.tsx10
-rw-r--r--src/components/nodes/Input.css103
-rw-r--r--src/components/nodes/Input.tsx97
-rw-r--r--src/components/nodes/NodeShell.css47
-rw-r--r--src/components/nodes/NodeShell.tsx46
-rw-r--r--src/components/nodes/Output.css20
-rw-r--r--src/components/nodes/Output.tsx19
-rw-r--r--src/components/nodes/Socket.css20
-rw-r--r--src/components/nodes/Socket.tsx41
-rw-r--r--src/components/nodes/index.ts3
-rw-r--r--src/context.ts5
-rw-r--r--src/node.css150
-rw-r--r--src/node.ts27
-rw-r--r--src/node.tsx208
-rw-r--r--src/nodes/CombineXYZ.tsx3
-rw-r--r--src/nodes/Fourier.tsx3
-rw-r--r--src/nodes/Intersperse.tsx3
-rw-r--r--src/nodes/Linspace.tsx3
-rw-r--r--src/nodes/Math.tsx3
-rw-r--r--src/nodes/Plot.tsx3
-rw-r--r--src/nodes/SeparateXYZ.tsx3
-rw-r--r--src/nodes/Unzip.tsx3
-rw-r--r--src/nodes/Viewer.tsx3
23 files changed, 446 insertions, 377 deletions
diff --git a/src/NodeEditor.tsx b/src/NodeEditor.tsx
index b3ef838..435aec4 100644
--- a/src/NodeEditor.tsx
+++ b/src/NodeEditor.tsx
@@ -1,8 +1,9 @@
import { useContext, useEffect, useMemo, useRef } from 'preact/hooks';
import { signal, computed, batch, useSignal, useComputed, Signal } from '@preact/signals';
import { Pb } from './context.ts';
+import { SocketHandlers } from './node.ts';
import { nodeRegistry } from './nodes';
-import { SocketHandlers, SocketHandler, NodeInfo } from './node.tsx';
+import type { SocketHandler, NodeInfo } from './node.tsx';
import { InputSocket } from './dataflow.ts';
import { Toolbar, ButtonMenu, MenuItem } from './components';
import './NodeEditor.css';
@@ -37,10 +38,7 @@ const Link = ({ fromX, fromY, toX, toY }: LinkProps) => {
const c1x = fromX.value + Math.abs(toX.value - fromX.value) / 3;
const c2x = toX.value - Math.abs(toX.value - fromX.value) / 3;
return (
- <path
- class={styles.link}
- d={`M ${fromX} ${fromY} C ${c1x} ${fromY} ${c2x} ${toY} ${toX} ${toY}`}
- />
+ <path class="link" d={`M ${fromX} ${fromY} C ${c1x} ${fromY} ${c2x} ${toY} ${toX} ${toY}`} />
);
};
@@ -78,8 +76,6 @@ const NodeEditor = ({ user, project }) => {
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);
}, []);
diff --git a/src/components/nodes/Input.css b/src/components/nodes/Input.css
new file mode 100644
index 0000000..aa01558
--- /dev/null
+++ b/src/components/nodes/Input.css
@@ -0,0 +1,103 @@
+@scope (.__Input) {
+ :scope {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ padding: 4px 0;
+
+ .__Output + & {
+ padding-top: 8px;
+ }
+
+ input {
+ background-color: var(--overlay);
+ border: none;
+ border-radius: 4px;
+ outline: none;
+ color: var(--text);
+ font-size: 1em;
+ text-align: right;
+ width: 100%;
+ max-width: 200px;
+ margin-right: 12px;
+ padding-top: 3px;
+ padding-bottom: 2px;
+
+ .linked & {
+ display: none;
+ }
+ }
+ }
+}
+
+@scope (.__InputNum) {
+ & + & {
+ padding-top: 0;
+ }
+
+ :scope {
+ --socket-color: var(--data-float);
+
+ span {
+ position: relative;
+ left: 8px;
+ width: 0;
+ white-space: nowrap;
+ padding-bottom: 2px;
+ }
+ }
+}
+
+@scope (.__InputVector) {
+ :scope {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1px;
+ --socket-color: var(--data-vector);
+
+ & :first-child {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 2px;
+ margin-bottom: 4px;
+ .linked & {
+ margin-bottom: 0;
+ }
+ }
+
+ input {
+ width: 89%;
+ margin-left: 8px;
+ margin-right: 16px;
+
+ &:nth-child(2) { border-radius: 4px 4px 0 0 }
+ &:nth-child(3) { border-radius: 0 }
+ &:nth-child(4) { border-radius: 0 0 4px 4px }
+
+ .linked & {
+ display: none;
+ }
+ }
+ }
+}
+
+@scope (.__InputSelect) {
+ :scope {
+ --socket-color: var(--data-float);
+
+ select {
+ background-color: color-mix(in srgb, var(--base) 50%, var(--surface));
+ color: var(--text);
+ border: 1px solid color-mix(in srgb, var(--overlay) 50%, var(--surface));
+ border-radius: 4px;
+ width: 100%;
+ margin-right: 12px;
+ padding: 3px 1px;
+
+ .linked & {
+ display: none;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/components/nodes/Input.tsx b/src/components/nodes/Input.tsx
new file mode 100644
index 0000000..1160f3f
--- /dev/null
+++ b/src/components/nodes/Input.tsx
@@ -0,0 +1,97 @@
+import { InputSocket } from '../../dataflow.ts';
+import { InSocket } from './Socket.tsx';
+import './Input.css';
+
+interface InProps {
+ children: ComponentChildren;
+ linked?: boolean;
+ class: string;
+}
+
+const Input = ({ children, linked = false, ...props }: InProps) => (
+ <li class={'__Input ' + props.class + (linked ? 'linked' : '')}>{children}</li>
+);
+
+export interface InputProps<T> {
+ name: string;
+ label: string;
+ value: InputSocket<T>;
+}
+
+export const InputAny = ({ name, label }: Omit<InputProps<any>, 'value'>) => {
+ return (
+ <Input class="__InputAny">
+ <InSocket name={name} />
+ {label}
+ </Input>
+ );
+};
+
+export const InputArray = ({ name, label }: Omit<InputProps<any>, 'value'>) => {
+ return (
+ <Input class="__InputArray">
+ <InSocket name={name} />
+ {label}
+ </Input>
+ );
+};
+
+const InputNum = (parseFunc: (string) => number) => ({ name, label, value }: InputProps<number>) => {
+ const onInput = (event: InputEvent) => {
+ value.value = parseFunc((event.target as HTMLInputElement).value);
+ }
+ return (
+ <Input class={'__InputNum' + (value.link.value ? ' linked' : '')}>
+ <InSocket name={name} />
+ <span>{label}</span>
+ <input type="number" value={value.manual.value} onInput={onInput} />
+ </Input>
+ );
+};
+
+export const InputInteger = InputNum(parseInt);
+export const InputNumber = InputNum(parseFloat);
+
+export const InputVector = ({ name, label, value }: InputProps<[number, number, number]>) => {
+ const onInput = (i: 0 | 1 | 2) => (event: InputEvent) => {
+ const newValue: [number, number, number] = [...value.value];
+ newValue[i] = parseFloat((event.target as HTMLInputElement).value);
+ value.value = newValue;
+ };
+ return (
+ <Input class={'__InputVector' + (value.link.value ? ' linked' : '')}>
+ <div>
+ <InSocket name={name} />
+ {label}
+ </div>
+ <input type="number" value={value.value[0]} onInput={onInput(0)} />
+ <input type="number" value={value.value[1]} onInput={onInput(1)} />
+ <input type="number" value={value.value[2]} onInput={onInput(2)} />
+ </Input>
+ );
+};
+
+export interface InputSelectProps extends InputProps<string> {
+ options: string[] | Record<string, string[]>;
+}
+
+export const InputSelect = ({ name, label, value, options }: InputSelectProps) => {
+ const onChange = (event: InputEvent) => {
+ value.value = (event.target as HTMLSelectElement).value;
+ }
+ return (
+ <Input class={'__InputSelect' + (value.link.value ? ' linked' : '')}>
+ <InSocket name={name} />
+ <select aria-label={label} onChange={onChange}>
+ {Array.isArray(options)
+ ? options.map(opt => <option value={opt}>{opt}</option>)
+ : Object.entries(options).map(([label, group]) => (
+ <optgroup label={label}>
+ {group.map(opt => <option value={opt}>{opt}</option>)}
+ </optgroup>
+ ))
+ }
+ </select>
+ </Input>
+ );
+};
diff --git a/src/components/nodes/NodeShell.css b/src/components/nodes/NodeShell.css
new file mode 100644
index 0000000..5e4fb2a
--- /dev/null
+++ b/src/components/nodes/NodeShell.css
@@ -0,0 +1,47 @@
+@scope (.__NodeShell) {
+ :scope {
+ width: 0;
+ height: 0;
+ overflow: visible;
+
+ > details {
+ background-color: var(--surface);
+ min-width: 180px;
+ width: fit-content;
+ font-size: 0.9rem;
+ margin: 4px;
+ padding-bottom: 8px;
+ display: flex;
+ flex-direction: column;
+ border-radius: 6px;
+ user-select: none;
+ --shadow-color: 0deg 0% 0%;
+ box-shadow:
+ 0.1px 0.4px 0.6px hsl(var(--shadow-color) / 0.04),
+ 0.5px 3.3px 4.8px -0.2px hsl(var(--shadow-color) / 0.15),
+ 1.4px 10.2px 14.7px -0.5px hsl(var(--shadow-color) / 0.27);
+
+ &:focus-within {
+ outline: 1px white solid;
+ }
+
+ summary {
+ background-color: var(--primary);
+ padding: 3px;
+ padding-left: 6px;
+ margin-bottom: 4px;
+ border-radius: 6px 6px 0 0;
+ list-style-image: url('../../icons/chevron-down.svg');
+ }
+ &:not([open]) summary {
+ list-style-image: url('../../icons/chevron-right.svg');
+ }
+
+ ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ }
+ }
+ }
+}
diff --git a/src/components/nodes/NodeShell.tsx b/src/components/nodes/NodeShell.tsx
new file mode 100644
index 0000000..0de1a6d
--- /dev/null
+++ b/src/components/nodes/NodeShell.tsx
@@ -0,0 +1,46 @@
+import { ComponentChildren } from 'preact';
+import { batch, Signal } from '@preact/signals';
+import { NodeId } from '../../node.ts';
+import './NodeShell.css';
+
+export interface NodeShellProps {
+ children: ComponentChildren;
+ id: number;
+ name: string;
+ x: Signal<number>;
+ y: Signal<number>;
+}
+
+const NodeShell = ({ children, id, name, x, y }: NodeShellProps) => {
+ const onMouseDown = (event: MouseEvent) => {
+ event.stopPropagation();
+
+ const onMouseMove = (event: MouseEvent) => batch(() => {
+ x.value += event.movementX;
+ y.value += event.movementY;
+ });
+
+ const onMouseUp = () => {
+ window.removeEventListener('mousemove', onMouseMove);
+ window.removeEventListener('mouseup', onMouseUp);
+ };
+
+ window.addEventListener('mousemove', onMouseMove);
+ window.addEventListener('mouseup', onMouseUp);
+ };
+
+ return (
+ <foreignObject x={x} y={y} width="0" height="0" class="__NodeShell">
+ <details open tabindex={0} class="node" onMouseDown={onMouseDown}>
+ <summary><span onClick={e => e.stopPropagation()}>{name}</span></summary>
+ <ul>
+ <NodeId.Provider value={id}>
+ {children}
+ </NodeId.Provider>
+ </ul>
+ </details>
+ </foreignObject>
+ );
+};
+
+export default NodeShell; \ No newline at end of file
diff --git a/src/components/nodes/Output.css b/src/components/nodes/Output.css
new file mode 100644
index 0000000..2c752a1
--- /dev/null
+++ b/src/components/nodes/Output.css
@@ -0,0 +1,20 @@
+@scope (.__Output) {
+ :scope {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 4px 0;
+ }
+}
+
+@scope (.__OutputNumber) {
+ :scope {
+ --socket-color: var(--data-float);
+ }
+}
+
+@scope (.__OutputVector) {
+ :scope {
+ --socket-color: var(--data-vector);
+ }
+} \ No newline at end of file
diff --git a/src/components/nodes/Output.tsx b/src/components/nodes/Output.tsx
new file mode 100644
index 0000000..8cd9c98
--- /dev/null
+++ b/src/components/nodes/Output.tsx
@@ -0,0 +1,19 @@
+import { OutSocket } from './Socket.tsx';
+import './Output.css';
+
+export interface OutputProps {
+ name: string;
+ label: string;
+}
+
+const Output = (type: string, cls: string) => ({ name, label }: OutputProps) => {
+ return (
+ <li class={cls + ' __Output out ' + type}>
+ {label}
+ <OutSocket name={name} />
+ </li>
+ );
+};
+
+export const OutputNumber = Output('number', '__OutputNumber');
+export const OutputVector = Output('vector', '__OutputVector');
diff --git a/src/components/nodes/Socket.css b/src/components/nodes/Socket.css
new file mode 100644
index 0000000..5879b32
--- /dev/null
+++ b/src/components/nodes/Socket.css
@@ -0,0 +1,20 @@
+@scope (.__Socket) {
+ :scope {
+ position: relative;
+ circle {
+ fill: var(--socket-color, var(--data-float));
+ }
+ }
+}
+
+@scope (.__InSocket) {
+ :scope {
+ right: 4px;
+ }
+}
+
+@scope (.__OutSocket) {
+ :scope {
+ left: 4px;
+ }
+}
diff --git a/src/components/nodes/Socket.tsx b/src/components/nodes/Socket.tsx
new file mode 100644
index 0000000..a4f38e1
--- /dev/null
+++ b/src/components/nodes/Socket.tsx
@@ -0,0 +1,41 @@
+import { useContext } from 'preact/hooks';
+import { NodeId, SocketHandlers } from '../../node.ts';
+import './Socket.css';
+
+interface SocketProps {
+ name: string;
+ class?: string;
+ onMouseDown?: SocketHandler;
+ onMouseUp?: SocketHandler;
+}
+
+const Socket = ({ name, onMouseDown, onMouseUp, ...props }: SocketProps) => {
+ const nodeId = useContext(NodeId);
+ const wrap = (func?: SocketHandler) => (event: MouseEvent) => func && func(nodeId, name, event);
+ return (
+ <svg
+ width="10"
+ height="10"
+ viewBox="0 0 16 16"
+ class={'__Socket ' + props.class}
+ onMouseDown={wrap(onMouseDown)}
+ onMouseUp={wrap(onMouseUp)}
+ >
+ <circle cx="8" cy="8" r="7" stroke="black" stroke-width="1" />
+ </svg>
+ );
+};
+
+export const InSocket = ({ name }: { name: string }) => {
+ const handlers = useContext(SocketHandlers);
+ return (
+ <Socket name={name} class="__InSocket" onMouseDown={handlers.onInMouseDown} onMouseUp={handlers.onInMouseUp} />
+ );
+};
+
+export const OutSocket = ({ name }: { name: string }) => {
+ const handlers = useContext(SocketHandlers);
+ return (
+ <Socket name={name} class="__OutSocket" onMouseDown={handlers.onOutMouseDown} onMouseUp={handlers.onOutMouseUp} />
+ );
+};
diff --git a/src/components/nodes/index.ts b/src/components/nodes/index.ts
new file mode 100644
index 0000000..d29f39f
--- /dev/null
+++ b/src/components/nodes/index.ts
@@ -0,0 +1,3 @@
+export { default as NodeShell, NodeShellProps } from './NodeShell.tsx';
+export { OutputNumber, OutputVector, OutputProps } from './Output.tsx';
+export { InputAny, InputArray, InputInteger, InputNumber, InputVector, InputSelect, InputProps, InputSelectProps } from './Input.tsx';
diff --git a/src/context.ts b/src/context.ts
index 2e77787..054d796 100644
--- a/src/context.ts
+++ b/src/context.ts
@@ -1,5 +1,4 @@
import { createContext } from 'preact';
+import type { PocketBase } from 'pocketbase';
-const Pb = createContext();
-
-export { Pb }; \ No newline at end of file
+export const Pb = createContext<PocketBase>();
diff --git a/src/node.css b/src/node.css
deleted file mode 100644
index 3b0ba16..0000000
--- a/src/node.css
+++ /dev/null
@@ -1,150 +0,0 @@
-@scope (.__NodeShell) {
- :scope {
- width: 0;
- height: 0;
- overflow: visible;
- }
-
- .node {
- background-color: var(--surface);
- min-width: 180px;
- width: fit-content;
- font-size: 0.9rem;
- margin: 4px;
- padding-bottom: 8px;
- display: flex;
- flex-direction: column;
- border-radius: 6px;
- user-select: none;
- --shadow-color: 0deg 0% 0%;
- box-shadow:
- 0.1px 0.4px 0.6px hsl(var(--shadow-color) / 0.04),
- 0.5px 3.3px 4.8px -0.2px hsl(var(--shadow-color) / 0.15),
- 1.4px 10.2px 14.7px -0.5px hsl(var(--shadow-color) / 0.27);
-
- &:focus-within {
- outline: 1px white solid;
- }
-
- input[type="number"] {
- background-color: var(--overlay);
- border: none;
- border-radius: 4px;
- outline: none;
- color: var(--text);
- font-size: 1em;
- text-align: right;
- width: 100%;
- max-width: 200px;
- margin-right: 12px;
- padding-top: 3px;
- padding-bottom: 2px;
- }
-
- summary {
- background-color: var(--primary);
- padding: 3px;
- padding-left: 6px;
- margin-bottom: 4px;
- border-radius: 6px 6px 0 0;
- list-style-image: url('icons/chevron-down.svg');
- }
- &:not([open]) summary {
- list-style-image: url('icons/chevron-right.svg');
- }
-
- ul {
- list-style: none;
- padding: 0;
- margin: 0;
- }
-
- ul li {
- display: flex;
- flex-direction: row;
- align-items: center;
- padding: 4px 0;
-
- circle {
- fill: var(--data-float);
- .number & { fill: var(--data-float) }
- .vector & { fill: var(--data-vector) }
- .select & { fill: var(--data-float) }
- }
-
- &.out + &.in {
- padding-top: 8px;
- }
-
- &.out {
- justify-content: flex-end;
-
- svg {
- position: relative;
- left: 4px;
- }
- }
-
- &.in {
- justify-content: flex-start;
-
- svg {
- position: relative;
- right: 4px;
- }
-
- &.number {
- + .number { padding-top: 0 }
- &.linked input { display: none }
-
- span {
- position: relative;
- left: 8px;
- width: 0;
- white-space: nowrap;
- padding-bottom: 2px;
- }
- }
-
- &.vector {
- flex-direction: column;
- align-items: flex-start;
- gap: 1px;
-
- & :first-child {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 2px;
- margin-bottom: 4px;
- .linked & { margin-bottom: 0 }
- }
-
- input {
- width: 89%;
- margin-left: 8px;
- margin-right: 16px;
-
- &:nth-child(2) { border-radius: 4px 4px 0 0 }
- &:nth-child(3) { border-radius: 0 }
- &:nth-child(4) { border-radius: 0 0 4px 4px }
-
- .linked & { display: none }
- }
- }
-
- &.select select {
- background-color: color-mix(in srgb, var(--base) 50%, var(--surface));
- color: var(--text);
- border: 1px solid color-mix(in srgb, var(--overlay) 50%, var(--surface));
- border-radius: 4px;
- width: 100%;
- margin-right: 12px;
- padding: 3px 1px;
-
- .linked & { display: none }
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/src/node.ts b/src/node.ts
new file mode 100644
index 0000000..1f4d5eb
--- /dev/null
+++ b/src/node.ts
@@ -0,0 +1,27 @@
+import { createContext, FunctionComponent } from 'preact';
+import { InputSocket } from './dataflow.ts';
+
+export type SocketHandler = (nodeId: number, socket: string, event: MouseEvent) => void;
+export interface SocketHandlers {
+ onOutMouseDown?: SocketHandler;
+ onOutMouseUp?: SocketHandler;
+ onInMouseDown?: SocketHandler;
+ onInMouseUp?: SocketHandler;
+}
+
+export interface NodeComponentProps<I extends {}> {
+ id: number;
+ x: Signal<number>;
+ y: Signal<number>;
+ inputs: { [Property in keyof I]: InputSocket<I[Property]> };
+}
+export type NodeComponent<I extends {}> = FunctionComponent<NodeComponentProps<I>>;
+
+export interface NodeInfo<I extends {}, O extends {}> {
+ component: NodeComponent<I>;
+ func: (inputs: I) => O;
+ inputs: I;
+}
+
+export const SocketHandlers = createContext<SocketHandlers>({});
+export const NodeId = createContext<number>(0);
diff --git a/src/node.tsx b/src/node.tsx
deleted file mode 100644
index cb72734..0000000
--- a/src/node.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import { createContext, FunctionComponent, ComponentChildren } from 'preact';
-import { useContext } from 'preact/hooks';
-import { batch, Signal } from '@preact/signals';
-import { InputSocket } from './dataflow.ts';
-import { cls } from './util.ts';
-import './node.css';
-
-export type SocketHandler = (nodeId: number, socket: string, event: MouseEvent) => void;
-export interface SocketHandlers {
- onOutMouseDown?: SocketHandler;
- onOutMouseUp?: SocketHandler;
- onInMouseDown?: SocketHandler;
- onInMouseUp?: SocketHandler;
-}
-
-export const SocketHandlers = createContext<SocketHandlers>({});
-export const NodeId = createContext<number>(0);
-
-export interface NodeComponentProps<I extends {}> {
- id: number;
- x: Signal<number>;
- y: Signal<number>;
- inputs: { [Property in keyof I]: InputSocket<I[Property]> };
-}
-export type NodeComponent<I extends {}> = FunctionComponent<NodeComponentProps<I>>;
-
-export interface NodeInfo<I extends {}, O extends {}> {
- component: NodeComponent<I>;
- func: (inputs: I) => O;
- inputs: I;
-}
-
-interface SocketProps {
- name: string;
- onMouseDown?: SocketHandler;
- onMouseUp?: SocketHandler;
-}
-
-const Socket = ({ name, onMouseDown, onMouseUp }: SocketProps) => {
- const nodeId = useContext(NodeId);
- const wrap = (func?: SocketHandler) => (event: MouseEvent) => func && func(nodeId, name, event);
- return (
- <svg
- width="10"
- height="10"
- viewBox="0 0 16 16"
- onMouseDown={wrap(onMouseDown)}
- onMouseUp={wrap(onMouseUp)}
- >
- <circle cx="8" cy="8" r="7" stroke="black" stroke-width="1" />
- </svg>
- );
-};
-
-const InSocket = ({ name }: { name: string }) => {
- const handlers = useContext(SocketHandlers);
- return (
- <Socket name={name} onMouseDown={handlers.onInMouseDown} onMouseUp={handlers.onInMouseUp} />
- );
-};
-
-const OutSocket = ({ name }: { name: string }) => {
- const handlers = useContext(SocketHandlers);
- return (
- <Socket name={name} onMouseDown={handlers.onOutMouseDown} onMouseUp={handlers.onOutMouseUp} />
- );
-};
-
-export interface InputProps<T> {
- name: string;
- label: string;
- value: InputSocket<T>;
-}
-
-export const InputAny = ({ name, label }: Omit<InputProps<any>, 'value'>) => {
- return (
- <li class="in">
- <InSocket name={name} />
- {label}
- </li>
- );
-};
-
-export const InputArray = ({ name, label }: Omit<InputProps<any>, 'value'>) => {
- return (
- <li class="in number">
- <InSocket name={name} />
- {label}
- </li>
- );
-};
-
-const InputNum = (parseFunc: (string) => number) => ({ name, label, value }: InputProps<number>) => {
- const onInput = (event: InputEvent) => {
- value.value = parseFunc((event.target as HTMLInputElement).value);
- }
- return (
- <li class={'in number' + (value.link.value ? ' linked' : '')}>
- <InSocket name={name} />
- <span>{label}</span>
- <input type="number" value={value.manual.value} onInput={onInput} />
- </li>
- );
-};
-
-export const InputNumber = InputNum(parseFloat);
-export const InputInteger = InputNum(parseInt);
-
-export const InputVector = ({ name, label, value }: InputProps<[number, number, number]>) => {
- const onInput = (i: 0 | 1 | 2) => (event: InputEvent) => {
- const newValue: [number, number, number] = [...value.value];
- newValue[i] = parseFloat((event.target as HTMLInputElement).value);
- value.value = newValue;
- };
- return (
- <li class={'in vector' + (value.link.value ? ' linked' : '')}>
- <div>
- <InSocket name={name} />
- {label}
- </div>
- <input type="number" value={value.value[0]} onInput={onInput(0)} />
- <input type="number" value={value.value[1]} onInput={onInput(1)} />
- <input type="number" value={value.value[2]} onInput={onInput(2)} />
- </li>
- );
-};
-
-export interface InputSelectProps extends InputProps<string> {
- options: string[] | Record<string, string[]>;
-}
-
-export const InputSelect = ({ name, label, value, options }: InputSelectProps) => {
- const onChange = (event: InputEvent) => {
- value.value = (event.target as HTMLSelectElement).value;
- }
- return (
- <li class={'in select' + (value.link.value ? ' linked' : '')}>
- <InSocket name={name} />
- <select aria-label={label} onChange={onChange}>
- {Array.isArray(options)
- ? options.map(opt => <option value={opt}>{opt}</option>)
- : Object.entries(options).map(([label, group]) => (
- <optgroup label={label}>
- {group.map(opt => <option value={opt}>{opt}</option>)}
- </optgroup>
- ))
- }
- </select>
- </li>
- );
-};
-
-export interface OutputProps {
- name: string;
- label: string;
-}
-
-const Output = ({ name, label, type }: OutputProps & { type: string }) => {
- return (
- <li class={'out ' + type}>
- {label}
- <OutSocket name={name} />
- </li>
- );
-};
-
-export const OutputNumber = (props: OutputProps) => <Output {...props} type="number" />;
-export const OutputVector = (props: OutputProps) => <Output {...props} type="vector" />;
-
-export interface NodeShellProps {
- id: number;
- name: string;
- x: Signal<number>;
- y: Signal<number>;
- children: ComponentChildren;
-}
-
-export const NodeShell = ({ id, name, x, y, children }: NodeShellProps) => {
- const onMouseDown = (event: MouseEvent) => {
- event.stopPropagation();
-
- const onMouseMove = (event: MouseEvent) => batch(() => {
- x.value += event.movementX;
- y.value += event.movementY;
- });
-
- const onMouseUp = () => {
- window.removeEventListener('mousemove', onMouseMove);
- window.removeEventListener('mouseup', onMouseUp);
- };
-
- window.addEventListener('mousemove', onMouseMove);
- window.addEventListener('mouseup', onMouseUp);
- };
-
- return (
- <foreignObject x={x} y={y} width="0" height="0" class="__NodeShell">
- <details open tabindex={0} class="node" onMouseDown={onMouseDown}>
- <summary><span onClick={e => e.stopPropagation()}>{name}</span></summary>
- <ul>
- <NodeId.Provider value={id}>
- {children}
- </NodeId.Provider>
- </ul>
- </details>
- </foreignObject>
- );
-}; \ No newline at end of file
diff --git a/src/nodes/CombineXYZ.tsx b/src/nodes/CombineXYZ.tsx
index 99823fd..5af22ce 100644
--- a/src/nodes/CombineXYZ.tsx
+++ b/src/nodes/CombineXYZ.tsx
@@ -1,4 +1,5 @@
-import { NodeShell, InputNumber, OutputVector, NodeComponentProps, NodeInfo } from '../node.tsx';
+import { NodeShell, InputNumber, OutputVector } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
export interface CombineXYZInputs {
x: number;
diff --git a/src/nodes/Fourier.tsx b/src/nodes/Fourier.tsx
index b9bda3f..f33768f 100644
--- a/src/nodes/Fourier.tsx
+++ b/src/nodes/Fourier.tsx
@@ -1,4 +1,5 @@
-import { NodeShell, InputArray, OutputNumber, NodeComponentProps, NodeInfo } from '../node.tsx';
+import { NodeShell, InputArray, OutputNumber } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
import { fft } from '../wasm.ts';
export interface FourierInputs {
diff --git a/src/nodes/Intersperse.tsx b/src/nodes/Intersperse.tsx
index 2ef2a03..0c7e271 100644
--- a/src/nodes/Intersperse.tsx
+++ b/src/nodes/Intersperse.tsx
@@ -1,4 +1,5 @@
-import { NodeShell, InputArray, OutputNumber, NodeComponentProps, NodeInfo } from '../node.tsx';
+import { NodeShell, InputArray, OutputNumber } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
import { intersperse } from '../wasm.ts';
export interface IntersperseInputs {
diff --git a/src/nodes/Linspace.tsx b/src/nodes/Linspace.tsx
index df1dbe6..b8239a1 100644
--- a/src/nodes/Linspace.tsx
+++ b/src/nodes/Linspace.tsx
@@ -1,4 +1,5 @@
-import { NodeShell, InputNumber, InputInteger, OutputNumber, NodeComponentProps, NodeInfo } from '../node.tsx';
+import { NodeShell, InputNumber, InputInteger, OutputNumber } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
import { linspace } from '../wasm.ts';
export interface LinspaceInputs {
diff --git a/src/nodes/Math.tsx b/src/nodes/Math.tsx
index fe46fb4..c885bae 100644
--- a/src/nodes/Math.tsx
+++ b/src/nodes/Math.tsx
@@ -1,5 +1,6 @@
+import { NodeShell, InputNumber, InputSelect, OutputNumber } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
import { mathS, mathV, mathSS, mathSV, mathVS, mathVV } from '../wasm.ts';
-import { NodeShell, InputNumber, InputSelect, OutputNumber, NodeComponentProps, NodeInfo } from '../node.tsx';
export enum MathOpFunc {
Add = 'Add',
diff --git a/src/nodes/Plot.tsx b/src/nodes/Plot.tsx
index 649246a..d87fb72 100644
--- a/src/nodes/Plot.tsx
+++ b/src/nodes/Plot.tsx
@@ -1,4 +1,5 @@
-import { NodeShell, InputAny, InputNumber, NodeComponentProps, NodeInfo } from '../node.tsx';
+import { NodeShell, InputAny, InputNumber } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
import { useComputed } from '@preact/signals';
export interface PlotInputs {
diff --git a/src/nodes/SeparateXYZ.tsx b/src/nodes/SeparateXYZ.tsx
index bc14be1..f76c43c 100644
--- a/src/nodes/SeparateXYZ.tsx
+++ b/src/nodes/SeparateXYZ.tsx
@@ -1,4 +1,5 @@
-import { NodeShell, InputVector, OutputNumber, NodeComponentProps, NodeInfo } from '../node.tsx';
+import { NodeShell, InputVector, OutputNumber } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
export interface SeparateXYZInputs {
vector: [number, number, number];
diff --git a/src/nodes/Unzip.tsx b/src/nodes/Unzip.tsx
index ff5832d..5bcb477 100644
--- a/src/nodes/Unzip.tsx
+++ b/src/nodes/Unzip.tsx
@@ -1,4 +1,5 @@
-import { NodeShell, InputArray, OutputNumber, NodeComponentProps, NodeInfo } from '../node.tsx';
+import { NodeShell, InputArray, OutputNumber } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
import { unzip } from '../wasm.ts';
export interface UnzipInputs {
diff --git a/src/nodes/Viewer.tsx b/src/nodes/Viewer.tsx
index b4b7758..d92da3c 100644
--- a/src/nodes/Viewer.tsx
+++ b/src/nodes/Viewer.tsx
@@ -1,4 +1,5 @@
-import { NodeShell, InputAny, NodeComponentProps, NodeInfo } from '../node.tsx';
+import { NodeShell, InputAny } from '../components/nodes';
+import type { NodeComponentProps, NodeInfo } from '../node.ts';
export interface ViewerInputs {
value: any;