/** * @file Particle Color Node - Provides particle color control output */ import * as React from "react"; import type { NodeDefinition, NodeRendererProps, InspectorRenderProps, ExternalDataReference, } from "../../../../../types/NodeDefinition"; import classes from "./ParticleColorNode.module.css"; export type ParticleColorData = { id: string; color: string; // rgb(...) format palette: string[]; }; const hexToRgb = (hex: string) => { const normalized = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex; const r = parseInt(normalized.slice(1, 3), 16); const g = parseInt(normalized.slice(3, 5), 16); const b = parseInt(normalized.slice(5, 7), 16); return `rgb(${r}, ${g}, ${b})`; }; const rgbToHex = (rgb: string) => { const match = rgb.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/); if (!match) { return "#f97316"; } const [r, g, b] = match.slice(1).map((component) => Number(component)); const toHex = (value: number) => value.toString(16).padStart(2, "0"); return `#${toHex(r)}${toHex(g)}${toHex(b)}`; }; const ensureRgb = (color: string) => { if (color.startsWith("rgb")) { return color; } if (color.startsWith("#")) { return hexToRgb(color); } return "rgb(249, 115, 22)"; }; const defaultPalette = ["#f97316", "#ec4899", "#8b5cf6", "#6366f1", "#22c55e", "#14b8a6"]; export const ParticleColorRenderer = ({ node, isSelected, isDragging, externalData, onUpdateNode, }: NodeRendererProps) => { const colorData = externalData as ParticleColorData | undefined; const rgbColor = ensureRgb(colorData?.color ?? "rgb(249, 115, 22)"); const [hexColor, setHexColor] = React.useState(rgbToHex(rgbColor)); const lastExternalColorRef = React.useRef(undefined); React.useEffect(() => { setHexColor(rgbToHex(ensureRgb(colorData?.color ?? rgbColor))); }, [colorData?.color, rgbColor]); React.useEffect(() => { if (!colorData?.color) { lastExternalColorRef.current = undefined; return; } const currentColor = ensureRgb(colorData.color); if (lastExternalColorRef.current === currentColor || node.data["color-output"] === currentColor) { return; } lastExternalColorRef.current = currentColor; onUpdateNode({ data: { ...node.data, color: currentColor, "color-output": currentColor, }, }); }, [colorData?.color, node.data, onUpdateNode]); const applyColor = (nextHex: string) => { const nextRgb = ensureRgb(nextHex); setHexColor(rgbToHex(nextRgb)); onUpdateNode({ data: { ...node.data, color: nextRgb, "color-output": nextRgb, updatedAt: Date.now(), }, }); }; const handlePaletteSelect = (nextHex: string) => { applyColor(nextHex); }; return (
🌈 Particle Color
{hexColor}
applyColor(event.target.value)} />
{(colorData?.palette ?? defaultPalette).map((paletteColor) => (
); }; export const ParticleColorInspectorRenderer = ({ externalData, onUpdateExternalData }: InspectorRenderProps) => { const colorData = externalData as ParticleColorData | undefined; const [editedData, setEditedData] = React.useState({ id: colorData?.id ?? "", color: ensureRgb(colorData?.color ?? "rgb(249, 115, 22)"), palette: colorData?.palette ?? defaultPalette, }); const handleSave = async () => { if (onUpdateExternalData) { await onUpdateExternalData(editedData); } }; const currentHex = rgbToHex(editedData.color); return (

Particle Color

{ const nextRgb = ensureRgb(event.target.value); setEditedData({ ...editedData, color: nextRgb }); }} />
setEditedData({ ...editedData, color: ensureRgb(event.target.value) })} />
); }; export const ParticleColorNodeDefinition: NodeDefinition = { type: "particle-color", displayName: "Particle Color", description: "Provides an RGB color output for particle systems", category: "Effects", defaultData: { title: "Particle Color", }, defaultSize: { width: 220, height: 160 }, ports: [ { id: "color-output", type: "output", label: "Color", position: "right", }, ], renderNode: ParticleColorRenderer, renderInspector: ParticleColorInspectorRenderer, loadExternalData: async (ref: ExternalDataReference) => { await new Promise((resolve) => setTimeout(resolve, 200)); return { id: ref.id, color: "rgb(249, 115, 22)", palette: defaultPalette, } satisfies ParticleColorData; }, updateExternalData: async (_ref: ExternalDataReference, data: unknown) => { await new Promise((resolve) => setTimeout(resolve, 200)); console.log("Updated particle color data:", data); }, };