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 styles from './node.module.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({}); export const NodeId = createContext(0); export interface NodeComponentProps { id: number; x: Signal; y: Signal; inputs: { [Property in keyof I]: InputSocket }; } export type NodeComponent = FunctionComponent>; export interface NodeInfo { component: NodeComponent; 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 ( ); }; const InSocket = ({ name }: { name: string }) => { const handlers = useContext(SocketHandlers); return ( ); }; const OutSocket = ({ name }: { name: string }) => { const handlers = useContext(SocketHandlers); return ( ); }; export interface InputProps { name: string; label: string; value: InputSocket; } export const InputAny = ({ name, label }: Omit, 'value'>) => { return (
  • {label}
  • ); }; export const InputArray = ({ name, label }: Omit, 'value'>) => { return (
  • {label}
  • ); }; const InputNum = (parseFunc: (string) => number) => ({ name, label, value }: InputProps) => { const onInput = (event: InputEvent) => { value.value = parseFunc((event.target as HTMLInputElement).value); } return (
  • {label}
  • ); }; 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 (
  • {label}
  • ); }; export interface InputSelectProps extends InputProps { options: string[] | Record; } export const InputSelect = ({ name, label, value, options }: InputSelectProps) => { const onChange = (event: InputEvent) => { value.value = (event.target as HTMLSelectElement).value; } return (
  • ); }; export interface OutputProps { name: string; label: string; } const Output = ({ name, label, type }: OutputProps & { type: string }) => { return (
  • {label}
  • ); }; export const OutputNumber = (props: OutputProps) => ; export const OutputVector = (props: OutputProps) => ; export interface NodeShellProps { id: number; name: string; x: Signal; y: Signal; 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 (
    e.stopPropagation()}>{name}
      {children}
    ); };