diff --git a/apps/electron-app/src/common/nodes.ts b/apps/electron-app/src/common/nodes.ts index 51e5f8d..1a12d53 100644 --- a/apps/electron-app/src/common/nodes.ts +++ b/apps/electron-app/src/common/nodes.ts @@ -1,71 +1,45 @@ -import { Button, DEFAULT_BUTTON_DATA } from '../render/components/react-flow/nodes/Button'; -import { Counter, DEFAULT_COUNTER_DATA } from '../render/components/react-flow/nodes/Counter'; -import { DEFAULT_FIGMA_DATA, Figma } from '../render/components/react-flow/nodes/Figma'; -import { DEFAULT_COMPARE_DATA, Compare } from '../render/components/react-flow/nodes/Compare'; -import { DEFAULT_INTERVAL_DATA, Interval } from '../render/components/react-flow/nodes/Interval'; -import { - DEFAULT_OSCILLATOR_DATA, - Oscillator, -} from '../render/components/react-flow/nodes/Oscillator'; -import { DEFAULT_TRIGGER_DATA, Trigger } from '../render/components/react-flow/nodes/Trigger'; -import { DEFAULT_SMOOTH_DATA, Smooth } from '../render/components/react-flow/nodes/Smooth'; -import { DEFAULT_LED_DATA, Led } from '../render/components/react-flow/nodes/Led'; -import { DEFAULT_MATRIX_DATA, Matrix } from '../render/components/react-flow/nodes/matrix/Matrix'; -import { DEFAULT_MOTION_DATA, Motion } from '../render/components/react-flow/nodes/Motion'; -import { DEFAULT_MQTT_DATA, Mqtt } from '../render/components/react-flow/nodes/Mqtt'; -import { DEFAULT_NOTE_DATA, Note } from '../render/components/react-flow/nodes/Note'; -import { DEFAULT_MONITOR_DATA, Monitor } from '../render/components/react-flow/nodes/Monitor'; -import { DEFAULT_PIEZO_DATA, Piezo } from '../render/components/react-flow/nodes/piezo/Piezo'; -import { DEFAULT_RANGE_MAP_DATA, RangeMap } from '../render/components/react-flow/nodes/RangeMap'; -import { DEFAULT_SENSOR_DATA, Sensor } from '../render/components/react-flow/nodes/Sensor'; -import { DEFAULT_SERVO_DATA, Servo } from '../render/components/react-flow/nodes/Servo'; -import { DEFAULT_RGB_DATA, Rgb } from '../render/components/react-flow/nodes/RGB'; -import { And, DEFAULT_AND_DATA } from '../render/components/react-flow/nodes/And'; +import { Button } from '../render/components/react-flow/nodes/Button'; +import { Counter } from '../render/components/react-flow/nodes/Counter'; +import { Compare } from '../render/components/react-flow/nodes/Compare'; +import { Figma } from '../render/components/react-flow/nodes/Figma'; +import { Interval } from '../render/components/react-flow/nodes/Interval'; +import { Led } from '../render/components/react-flow/nodes/Led'; +import { Matrix } from '../render/components/react-flow/nodes/matrix/Matrix'; +import { Monitor } from '../render/components/react-flow/nodes/Monitor'; +import { Motion } from '../render/components/react-flow/nodes/Motion'; +import { Mqtt } from '../render/components/react-flow/nodes/Mqtt'; +import { Note } from '../render/components/react-flow/nodes/Note'; +import { Oscillator } from '../render/components/react-flow/nodes/Oscillator'; +import { Piezo } from '../render/components/react-flow/nodes/piezo/Piezo'; +import { RangeMap } from '../render/components/react-flow/nodes/RangeMap'; +import { Rgb } from '../render/components/react-flow/nodes/RGB'; +import { Sensor } from '../render/components/react-flow/nodes/Sensor'; +import { Servo } from '../render/components/react-flow/nodes/Servo'; +import { Smooth } from '../render/components/react-flow/nodes/Smooth'; +import { Trigger } from '../render/components/react-flow/nodes/Trigger'; +import { Gate } from '../render/components/react-flow/nodes/Gate'; export const NODE_TYPES = { - And: And, Button: Button, + Compare: Compare, Counter: Counter, - Monitor: Monitor, Figma: Figma, - Compare: Compare, + Gate: Gate, Interval: Interval, - Oscillator: Oscillator, - Trigger: Trigger, - Smooth: Smooth, Led: Led, Matrix: Matrix, + Monitor: Monitor, Motion: Motion, Mqtt: Mqtt, Note: Note, + Oscillator: Oscillator, Piezo: Piezo, RangeMap: RangeMap, Rgb: Rgb, Sensor: Sensor, Servo: Servo, + Smooth: Smooth, + Trigger: Trigger, }; export type NodeType = keyof typeof NODE_TYPES; - -export const DEFAULT_NODE_DATA = new Map>(); - -DEFAULT_NODE_DATA.set('And', DEFAULT_AND_DATA); -DEFAULT_NODE_DATA.set('Button', DEFAULT_BUTTON_DATA); -DEFAULT_NODE_DATA.set('Counter', DEFAULT_COUNTER_DATA); -DEFAULT_NODE_DATA.set('Monitor', DEFAULT_MONITOR_DATA); -DEFAULT_NODE_DATA.set('Figma', DEFAULT_FIGMA_DATA); -DEFAULT_NODE_DATA.set('Compare', DEFAULT_COMPARE_DATA); -DEFAULT_NODE_DATA.set('Interval', DEFAULT_INTERVAL_DATA); -DEFAULT_NODE_DATA.set('Oscillator', DEFAULT_OSCILLATOR_DATA); -DEFAULT_NODE_DATA.set('Trigger', DEFAULT_TRIGGER_DATA); -DEFAULT_NODE_DATA.set('Smooth', DEFAULT_SMOOTH_DATA); -DEFAULT_NODE_DATA.set('Led', DEFAULT_LED_DATA); -DEFAULT_NODE_DATA.set('Matrix', DEFAULT_MATRIX_DATA); -DEFAULT_NODE_DATA.set('Motion', DEFAULT_MOTION_DATA); -DEFAULT_NODE_DATA.set('Mqtt', DEFAULT_MQTT_DATA); -DEFAULT_NODE_DATA.set('Note', DEFAULT_NOTE_DATA); -DEFAULT_NODE_DATA.set('Piezo', DEFAULT_PIEZO_DATA); -DEFAULT_NODE_DATA.set('RangeMap', DEFAULT_RANGE_MAP_DATA); -DEFAULT_NODE_DATA.set('Rgb', DEFAULT_RGB_DATA); -DEFAULT_NODE_DATA.set('Sensor', DEFAULT_SENSOR_DATA); -DEFAULT_NODE_DATA.set('Servo', DEFAULT_SERVO_DATA); diff --git a/apps/electron-app/src/render/components/react-flow/nodes/And.tsx b/apps/electron-app/src/render/components/react-flow/nodes/And.tsx deleted file mode 100644 index 021280a..0000000 --- a/apps/electron-app/src/render/components/react-flow/nodes/And.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Position } from '@xyflow/react'; -import { Handle } from './Handle'; -import { BaseNode, NodeContainer, useNodeData, useNodeSettings } from './Node'; -import { type AndData, type AndValueType } from '@microflow/components'; -import { useNodeValue } from '../../../stores/node-data'; -import { useEffect } from 'react'; -import { Icons } from '@ui/index'; -import { uuid } from '../../../../utils/uuid'; - -export function And(props: Props) { - function getOffset(index: number) { - return index - (props.data.checks - 1) / 2; - } - - return ( - - - - {Array.from({ length: props.data.checks }).map((_item, index) => { - return ( - - ); - })} - - - - - ); -} - -function Value() { - const data = useNodeData(); - const value = useNodeValue([]); - - const allTrue = value.length === data.checks && value.every(Boolean); - - if (allTrue) return ; - return ; -} - -function Settings() { - const { pane, settings, setHandlesToDelete } = useNodeSettings(); - - useEffect(() => { - if (!pane) return; - - const initialAmount = Number(settings.checks); - - pane - .addBinding(settings, 'checks', { - index: 0, - min: 2, - step: 1, - max: 4, - }) - .on('change', ({ value }) => { - if (value === initialAmount) { - setHandlesToDelete([]); - return; - } - - setHandlesToDelete(Array.from({ length: value }).map((_, index) => String(index))); - }); - }, [pane, settings]); - - return null; -} - -type Props = BaseNode; -export const DEFAULT_AND_DATA: Props['data'] = { - label: 'and', - checks: 4, -}; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Button.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Button.tsx index 2d38f8e..8d39aac 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Button.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Button.tsx @@ -94,11 +94,13 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_BUTTON_DATA: Props['data'] = { - holdtime: 500, - isPulldown: false, - isPullup: false, - invert: false, - pin: 6, - label: 'Button', +Button.defaultProps = { + data: { + holdtime: 500, + isPulldown: false, + isPullup: false, + invert: false, + pin: 6, + label: 'Button', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Compare.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Compare.tsx index ef6c8a7..fbb3909 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Compare.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Compare.tsx @@ -141,7 +141,9 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_COMPARE_DATA: Props['data'] = { - label: 'compare', - validator: 'boolean', +Compare.defaultProps = { + data: { + label: 'compare', + validator: 'boolean', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Counter.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Counter.tsx index 068723b..862660b 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Counter.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Counter.tsx @@ -26,6 +26,8 @@ function Value() { } type Props = BaseNode; -export const DEFAULT_COUNTER_DATA: Props['data'] = { - label: 'Counter', +Counter.defaultProps = { + data: { + label: 'Counter', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Figma.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Figma.tsx index 59c4c3d..b7b8e15 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Figma.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Figma.tsx @@ -227,10 +227,13 @@ function Value(props: { variable?: FigmaVariable; hasVariables: boolean }) { type Props = BaseNode; const DEFAULT_COLOR: RGBA = { r: 0, g: 0, b: 0, a: 1 }; -export const DEFAULT_FIGMA_DATA: Props['data'] = { - label: 'Figma', - variableId: '', +Figma.defaultProps = { + data: { + label: 'Figma', + variableId: '', + } satisfies Props['data'], }; + export const DEFAULT_FIGMA_VALUE_PER_TYPE: Record = { BOOLEAN: false, FLOAT: 0, diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Gate.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Gate.tsx new file mode 100644 index 0000000..cd988b3 --- /dev/null +++ b/apps/electron-app/src/render/components/react-flow/nodes/Gate.tsx @@ -0,0 +1,223 @@ +import { Position } from '@xyflow/react'; +import { Handle } from './Handle'; +import { BaseNode, NodeContainer, useNodeData, useNodeSettings } from './Node'; +import { type GateData, type GateValueType } from '@microflow/components'; +import { useNodeValue } from '../../../stores/node-data'; +import { useEffect } from 'react'; +import { Icons } from '@ui/index'; +import { uuid } from '../../../../utils/uuid'; + +export function Gate(props: Props) { + function getOffset(index: number) { + return index - (props.data.inputs - 1) / 2; + } + + return ( + + + + {Array.from({ length: props.data.inputs }).map((_item, index) => { + return ( + + ); + })} + + + + + ); +} + +function Value() { + const data = useNodeData(); + const value = useNodeValue(false); + + return ; +} + +function Settings() { + const { pane, settings, setHandlesToDelete } = useNodeSettings(); + + useEffect(() => { + if (!pane) return; + + const initialAmount = Number(settings.inputs); + + pane.addBinding(settings, 'gate', { + index: 0, + type: 'list', + options: [ + { text: 'not', value: 'not' }, + { text: 'and', value: 'and' }, + { text: 'nand', value: 'nand' }, + { text: 'or', value: 'or' }, + { text: 'nor', value: 'nor' }, + { text: 'xor', value: 'xor' }, + { text: 'xnor', value: 'xnor' }, + ], + }); + + pane + .addBinding(settings, 'inputs', { + index: 1, + min: 2, + step: 1, + max: 4, + }) + .on('change', ({ value }) => { + if (value === initialAmount) { + setHandlesToDelete([]); + return; + } + + setHandlesToDelete(Array.from({ length: value }).map((_, index) => String(index))); + }); + }, [pane, settings]); + + return null; +} + +type Props = BaseNode; +Gate.defaultProps = { + data: { + label: 'Gate', + gate: 'and', + inputs: 2, + } satisfies Props['data'], +}; + +const DEFAULT_ICON_SIZE = 60; +function GateIcon(props: { gate: GateData['gate']; size?: number; className?: string }) { + switch (props.gate) { + case 'not': + return ( + + + + + ); + case 'and': + return ( + + + + ); + case 'nand': + return ( + + + + + ); + case 'or': + return ( + + + + ); + case 'nor': + return ( + + + + + ); + case 'xor': + return ( + + + + + ); + case 'xnor': + return ( + + + + + + ); + default: + return
...
; + } +} diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Interval.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Interval.tsx index a0470b1..54824e5 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Interval.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Interval.tsx @@ -43,7 +43,9 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_INTERVAL_DATA: Props['data'] = { - label: 'Interval', - interval: 500, +Interval.defaultProps = { + data: { + label: 'Interval', + interval: 500, + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Led.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Led.tsx index 7ae5b78..22b181c 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Led.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Led.tsx @@ -102,7 +102,9 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_LED_DATA: Props['data'] = { - label: 'LED', - pin: 13, +Led.defaultProps = { + data: { + label: 'LED', + pin: 13, + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Monitor.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Monitor.tsx index 242f4ca..1d2df95 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Monitor.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Monitor.tsx @@ -136,8 +136,10 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_MONITOR_DATA: Props['data'] = { - label: 'Monitor', - type: 'graph', - range: { min: 0, max: 1023 }, +Monitor.defaultProps = { + data: { + label: 'Monitor', + type: 'graph', + range: { min: 0, max: 1023 }, + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Motion.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Motion.tsx index e5f24e8..94f0394 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Motion.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Motion.tsx @@ -97,8 +97,10 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_MOTION_DATA: Props['data'] = { - pin: '8', - label: 'Motion', - controller: 'HCSR501', +Motion.defaultProps = { + data: { + pin: '8', + label: 'Motion', + controller: 'HCSR501', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Mqtt.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Mqtt.tsx index 019b749..eeee993 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Mqtt.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Mqtt.tsx @@ -105,8 +105,10 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_MQTT_DATA: Props['data'] = { - label: 'MQTT', - direction: 'publish', - topic: '', +Mqtt.defaultProps = { + data: { + label: 'MQTT', + direction: 'publish', + topic: '', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Node.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Node.tsx index 4ddaa19..6b4deea 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Node.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Node.tsx @@ -337,8 +337,8 @@ const node = cva( }, ); -export type BaseNode = {}> = Node< - Attributes & { +export type BaseNode = {}> = Node< + Data & { type?: string; subType?: string; label: string; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Note.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Note.tsx index 866615f..07af273 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Note.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Note.tsx @@ -43,8 +43,10 @@ type NoteData = { note: string; }; type Props = BaseNode; -export const DEFAULT_NOTE_DATA: Props['data'] = { - label: 'Note', - note: 'New note', - extraInfo: '', +Note.defaultProps = { + data: { + label: 'Note', + note: 'New note', + extraInfo: '', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Oscillator.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Oscillator.tsx index 91627ce..efe3a9c 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Oscillator.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Oscillator.tsx @@ -76,11 +76,13 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_OSCILLATOR_DATA: Props['data'] = { - label: 'Oscillator', - waveform: 'sinus', - period: 1000, - amplitude: 1, - phase: 0, - shift: 0, +Oscillator.defaultProps = { + data: { + label: 'Oscillator', + waveform: 'sinus', + period: 1000, + amplitude: 1, + phase: 0, + shift: 0, + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/RGB.tsx b/apps/electron-app/src/render/components/react-flow/nodes/RGB.tsx index 5df3fe6..9961552 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/RGB.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/RGB.tsx @@ -69,12 +69,14 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_RGB_DATA: Props['data'] = { - label: 'RGB', - pins: { - red: 9, - green: 10, - blue: 11, - }, - isAnode: false, +Rgb.defaultProps = { + data: { + label: 'RGB', + pins: { + red: 9, + green: 10, + blue: 11, + }, + isAnode: false, + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/RangeMap.tsx b/apps/electron-app/src/render/components/react-flow/nodes/RangeMap.tsx index ad305f5..4e8e409 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/RangeMap.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/RangeMap.tsx @@ -54,9 +54,10 @@ function Settings() { } type Props = BaseNode; - -export const DEFAULT_RANGE_MAP_DATA: Props['data'] = { - from: { min: 0, max: 1023 }, - to: { min: 0, max: 1023 }, - label: 'Map', +RangeMap.defaultProps = { + data: { + from: { min: 0, max: 1023 }, + to: { min: 0, max: 1023 }, + label: 'Map', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Sensor.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Sensor.tsx index 4187905..6df736d 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Sensor.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Sensor.tsx @@ -56,7 +56,9 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_SENSOR_DATA: Props['data'] = { - pin: 'A0', - label: 'Sensor', +Sensor.defaultProps = { + data: { + pin: 'A0', + label: 'Sensor', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Servo.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Servo.tsx index 0122edd..89509d0 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Servo.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Servo.tsx @@ -134,9 +134,11 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_SERVO_DATA: Props['data'] = { - pin: 3, - label: 'Servo', - type: 'standard', - range: { min: 0, max: 180 }, +Servo.defaultProps = { + data: { + pin: 3, + label: 'Servo', + type: 'standard', + range: { min: 0, max: 180 }, + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Smooth.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Smooth.tsx index 636ab75..12b7635 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Smooth.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Smooth.tsx @@ -40,7 +40,9 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_SMOOTH_DATA: Props['data'] = { - label: 'Smooth', - attenuation: 0.995, +Smooth.defaultProps = { + data: { + label: 'Smooth', + attenuation: 0.995, + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Trigger.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Trigger.tsx index 9c7b97e..77606c2 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Trigger.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Trigger.tsx @@ -64,9 +64,11 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_TRIGGER_DATA: Props['data'] = { - label: 'Trigger', - behaviour: 'exact', - threshold: 0.5, - duration: 250, +Trigger.defaultProps = { + data: { + label: 'Trigger', + behaviour: 'exact', + threshold: 0.5, + duration: 250, + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/matrix/Matrix.tsx b/apps/electron-app/src/render/components/react-flow/nodes/matrix/Matrix.tsx index 0e8bf50..003f04d 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/matrix/Matrix.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/matrix/Matrix.tsx @@ -202,14 +202,16 @@ function Settings() { } type Props = BaseNode; -export const DEFAULT_MATRIX_DATA: Props['data'] = { - label: 'LED Matrix', - pins: { - data: 2, - clock: 3, - cs: 4, - }, - controller: undefined, - dims: [8, 8], // [rows, columns] - shapes: [DEFAULT_MATRIX_SHAPE], +Matrix.defaultProps = { + data: { + label: 'LED Matrix', + pins: { + data: 2, + clock: 3, + cs: 4, + }, + controller: undefined as unknown as string, + dims: [8, 8], // [rows, columns] + shapes: [DEFAULT_MATRIX_SHAPE], + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/components/react-flow/nodes/piezo/Piezo.tsx b/apps/electron-app/src/render/components/react-flow/nodes/piezo/Piezo.tsx index a20e89e..e00f679 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/piezo/Piezo.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/piezo/Piezo.tsx @@ -153,10 +153,12 @@ export const DEFAULT_FREQUENCY = NOTES_AND_FREQUENCIES.get(DEFAULT_NOTE); // @ts-expect-error PiezoData is not of type `Record` type Props = BaseNode; -export const DEFAULT_PIEZO_DATA: Props['data'] = { - label: 'Piezo', - duration: 500, - frequency: DEFAULT_FREQUENCY!, - pin: 11, - type: 'buzz', +Piezo.defaultProps = { + data: { + label: 'Piezo', + duration: 500, + frequency: DEFAULT_FREQUENCY!, + pin: 11, + type: 'buzz', + } satisfies Props['data'], }; diff --git a/apps/electron-app/src/render/providers/NewNodeProvider.tsx b/apps/electron-app/src/render/providers/NewNodeProvider.tsx index 2f2332a..f563ef8 100644 --- a/apps/electron-app/src/render/providers/NewNodeProvider.tsx +++ b/apps/electron-app/src/render/providers/NewNodeProvider.tsx @@ -14,7 +14,7 @@ import { } from '@microflow/ui'; import { useReactFlow } from '@xyflow/react'; import { memo, useEffect, useMemo } from 'react'; -import { DEFAULT_NODE_DATA, NodeType } from '../../common/nodes'; +import { NODE_TYPES, NodeType } from '../../common/nodes'; import { useDeleteSelectedNodes, useNodesChange } from '../stores/react-flow'; import { useNewNodeStore } from '../stores/new-node'; import { useWindowSize } from 'usehooks-ts'; @@ -40,16 +40,14 @@ export const NewNodeCommandDialog = memo(function NewNodeCommandDialog() { }); }, [flowToScreenPosition, windowSize]); - function selectNode(type: NodeType, options?: { label?: string; subType?: string }) { + function selectNode(type: NodeType, data?: { label?: string; subType?: string }) { return function () { - const DEFAULT_DATA = DEFAULT_NODE_DATA.get(type) ?? {}; + const DEFAULT_DATA = + 'defaultProps' in NODE_TYPES[type] ? (NODE_TYPES[type].defaultProps as any).data : {}; const id = Math.random().toString(36).substring(2, 8); const newNode = { - data: { - ...DEFAULT_DATA, - ...options, - }, + data: { ...DEFAULT_DATA, ...data }, id, type, position, @@ -107,8 +105,8 @@ export const NewNodeCommandDialog = memo(function NewNodeCommandDialog() { Control - - And + + Gate Control diff --git a/apps/electron-app/src/utils/generateCode.ts b/apps/electron-app/src/utils/generateCode.ts index 87a9ca4..30bc2c8 100644 --- a/apps/electron-app/src/utils/generateCode.ts +++ b/apps/electron-app/src/utils/generateCode.ts @@ -53,6 +53,16 @@ if (!port) { innerCode += addEnter(); innerCode += addEnter(); + // Initial triggers + nodes.forEach(node => { + if (node.type?.toLowerCase() === 'gate') { + const trigger = ` ${node.type}_${node.id}.check(${getAllEdgesValues(node.id, edges, nodes)});`; + innerCode += wrapInTryCatch(trigger); + innerCode += addEnter(); + innerCode += addEnter(); + } + }); + const nodesWithActionListener = nodes.filter(node => edges.some(edge => edge.source === node.id)); nodesWithActionListener.forEach(node => { @@ -66,6 +76,7 @@ if (!port) { {} as Record, ); + // React to actions Object.entries(actionsGroupedByHandle).forEach(([action, actionEdges]) => { innerCode += ` ${node.type}_${node.id}.on("${action}", () => {`; @@ -75,22 +86,14 @@ if (!port) { let value = `${node.type}_${node.id}.value`; - if (node.type === 'RangeMap') { + if (node.type?.toLowerCase() === 'rangemap') { // Mapper node innerCode += addEnter(); value = `${node.type}_${node.id}.value[1]`; } - if (targetNode.type === 'And') { - // We are handling the `And` node differently - const nodeValues = edges - .filter(edge => edge.target === targetNode.id) - .map(edge => { - const sourceNode = nodes.find(node => node.id === edge.source); - return `${sourceNode?.type}_${sourceNode?.id}.value`; - }) - .join(','); - value = `[${nodeValues}]`; + if (targetNode.type?.toLowerCase() === 'gate') { + value = getAllEdgesValues(targetNode.id, edges, nodes); edge.targetHandle = 'check'; // Naughty override, make sure this is in line with the actual implementation } @@ -199,3 +202,19 @@ try { log.error("something went wrong", { error }); }`; } + +/** + * @returns {string} The values of the source node as a string array + * @example `'[true, 5, 0, 'what']'` + */ +function getAllEdgesValues(nodeId: string, edges: Edge[], nodes: Node[]) { + const values = edges + .filter(edge => edge.target === nodeId) + .map(edge => { + const sourceNode = nodes.find(node => node.id === edge.source); + return `${sourceNode?.type}_${sourceNode?.id}.value`; + }) + .join(','); + + return `[${values}]`; +} diff --git a/apps/nextjs-app/app/docs/contributing/nodes/page.md b/apps/nextjs-app/app/docs/contributing/nodes/page.md index 01000cb..de3fa03 100644 --- a/apps/nextjs-app/app/docs/contributing/nodes/page.md +++ b/apps/nextjs-app/app/docs/contributing/nodes/page.md @@ -103,17 +103,19 @@ function Settings() { ```ts type Props = BaseNode; -export const DEFAULT_MYNODE_DATA: Props['data'] = { - label: 'MyNode', - emotion: 'happy', - joy: 95, +MyNode.defaultProps = { + data: { + label: 'MyNode', + emotion: 'happy', + joy: 95, + } satisfies Props['data'], }; ``` - Add a reference in `apps/electron-app/src/common/nodes.ts` ```ts -import { DEFAULT_MYNODE_DATA, MyNode } from '../render/components/react-flow/nodes/MyNode'; +import { MyNode } from '../render/components/react-flow/nodes/MyNode'; ``` Add the correct entry to the `NODE_TYPES` list: @@ -126,13 +128,7 @@ export const NODE_TYPES = { }; ``` -And last but not least in that same file, add an entry that specifies the default attribute values for the node: - -```ts -DEFAULT_MYNODE_DATA.set('MyNode', DEFAULT_MYNODE_DATA); -``` - -- Add some JSX in `apps/electron-app/src/render/NewNodeProvider.tsx` so that it appears in the search menu. +And last but not least, add some JSX in `apps/electron-app/src/render/NewNodeProvider.tsx` so that it appears in the search menu. ```tsx diff --git a/packages/components/index.ts b/packages/components/index.ts index 56a5387..eeba85e 100644 --- a/packages/components/index.ts +++ b/packages/components/index.ts @@ -1,9 +1,9 @@ export { Board } from 'johnny-five'; -export * from './src/components/And'; export * from './src/components/Button'; export * from './src/components/Compare'; export * from './src/components/Counter'; export * from './src/components/Figma'; +export * from './src/components/Gate'; export * from './src/components/Interval'; export * from './src/components/Oscillator'; export * from './src/components/Trigger'; diff --git a/packages/components/src/components/And.ts b/packages/components/src/components/And.ts deleted file mode 100644 index f4858be..0000000 --- a/packages/components/src/components/And.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { BaseComponent, BaseComponentData } from './BaseComponent'; - -export type AndValueType = boolean[]; - -export type AndData = { - checks: number; -}; - -export class And extends BaseComponent { - constructor(private readonly data: BaseComponentData & AndData) { - super(data, Array.from({ length: data.checks }).fill(false).map(Boolean)); - } - - check(inputs: unknown[]) { - this.value = inputs.map(input => - ['1', 'true', 'on', 'yes'].includes(String(input).toLowerCase()), - ); - - const amountOfTrue = Object.values(this.value).filter(Boolean).length; - this.eventEmitter.emit(amountOfTrue === this.data.checks ? 'true' : 'false', this.value, false); - } -} diff --git a/packages/components/src/components/Gate.ts b/packages/components/src/components/Gate.ts new file mode 100644 index 0000000..1b751b7 --- /dev/null +++ b/packages/components/src/components/Gate.ts @@ -0,0 +1,49 @@ +import { BaseComponent, BaseComponentData } from './BaseComponent'; + +export type GateValueType = boolean; + +type GateType = 'or' | 'and' | 'xor' | 'not' | 'nor' | 'nand' | 'xnor'; + +export type GateData = { + gate: GateType; + inputs: number; +}; + +export class Gate extends BaseComponent { + constructor(private readonly data: BaseComponentData & GateData) { + super(data, false); + } + + check(inputs: unknown[]) { + const inputsAsBooleans = inputs.map(input => + ['1', 'true', 'on', 'yes'].includes(String(input).toLowerCase()), + ); + + this.value = this.passesGate(inputsAsBooleans); + + this.eventEmitter.emit(this.value ? 'true' : 'false', this.value); + } + + private passesGate(inputs: boolean[]) { + const amountOfTrue = inputs.filter(Boolean).length; + console.log(`Amount true: ${amountOfTrue}, inputs: ${this.data.inputs}`); + switch (this.data.gate) { + case 'not': + return !(amountOfTrue === this.data.inputs); + case 'and': + return amountOfTrue === this.data.inputs; + case 'nand': + return amountOfTrue !== this.data.inputs; + case 'or': + return amountOfTrue > 0; + case 'xor': + return amountOfTrue === 1; + case 'nor': + return amountOfTrue === 0; + case 'xnor': + return amountOfTrue === 0 || amountOfTrue === this.data.inputs; + default: + return false; + } + } +}