diff options
| -rw-r--r-- | src/NodeEditor.tsx | 10 | ||||
| -rw-r--r-- | src/components/nodes/Input.css | 103 | ||||
| -rw-r--r-- | src/components/nodes/Input.tsx | 97 | ||||
| -rw-r--r-- | src/components/nodes/NodeShell.css | 47 | ||||
| -rw-r--r-- | src/components/nodes/NodeShell.tsx | 46 | ||||
| -rw-r--r-- | src/components/nodes/Output.css | 20 | ||||
| -rw-r--r-- | src/components/nodes/Output.tsx | 19 | ||||
| -rw-r--r-- | src/components/nodes/Socket.css | 20 | ||||
| -rw-r--r-- | src/components/nodes/Socket.tsx | 41 | ||||
| -rw-r--r-- | src/components/nodes/index.ts | 3 | ||||
| -rw-r--r-- | src/context.ts | 5 | ||||
| -rw-r--r-- | src/node.css | 150 | ||||
| -rw-r--r-- | src/node.ts | 27 | ||||
| -rw-r--r-- | src/node.tsx | 208 | ||||
| -rw-r--r-- | src/nodes/CombineXYZ.tsx | 3 | ||||
| -rw-r--r-- | src/nodes/Fourier.tsx | 3 | ||||
| -rw-r--r-- | src/nodes/Intersperse.tsx | 3 | ||||
| -rw-r--r-- | src/nodes/Linspace.tsx | 3 | ||||
| -rw-r--r-- | src/nodes/Math.tsx | 3 | ||||
| -rw-r--r-- | src/nodes/Plot.tsx | 3 | ||||
| -rw-r--r-- | src/nodes/SeparateXYZ.tsx | 3 | ||||
| -rw-r--r-- | src/nodes/Unzip.tsx | 3 | ||||
| -rw-r--r-- | src/nodes/Viewer.tsx | 3 |
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; |
