"use client"; import { useMemo, useState, type ChangeEvent, type FormEvent } from "react"; import { Position, type NodeProps } from "@xyflow/react"; import BaseNodeWrapper from "./base-node-wrapper"; import { useCanvasGraph } from "@/components/canvas/canvas-graph-context"; import { useCanvasSync } from "@/components/canvas/canvas-sync-context"; import { normalizeMixerPreviewData, resolveMixerPreviewFromGraph, type MixerBlendMode, } from "@/lib/canvas-mixer-preview"; import type { Id } from "@/convex/_generated/dataModel"; import CanvasHandle from "@/components/canvas/canvas-handle"; const BLEND_MODE_OPTIONS: MixerBlendMode[] = ["normal", "multiply", "screen", "overlay"]; export default function MixerNode({ id, data, selected }: NodeProps) { const graph = useCanvasGraph(); const { queueNodeDataUpdate } = useCanvasSync(); const [hasImageLoadError, setHasImageLoadError] = useState(false); const normalizedData = useMemo(() => normalizeMixerPreviewData(data), [data]); const previewState = useMemo( () => resolveMixerPreviewFromGraph({ nodeId: id, graph }), [graph, id], ); const currentData = (data ?? {}) as Record; const updateData = (patch: Partial>) => { void queueNodeDataUpdate({ nodeId: id as Id<"nodes">, data: { ...currentData, ...patch, }, }); }; const onBlendModeChange = (event: ChangeEvent) => { setHasImageLoadError(false); updateData({ blendMode: event.target.value as MixerBlendMode }); }; const onNumberChange = (field: "opacity" | "offsetX" | "offsetY") => ( event: FormEvent, ) => { setHasImageLoadError(false); const nextValue = Number(event.currentTarget.value); updateData({ [field]: Number.isFinite(nextValue) ? nextValue : 0 }); }; const showReadyPreview = previewState.status === "ready" && !hasImageLoadError; const showPreviewError = hasImageLoadError || previewState.status === "error"; return (
Mixer
{showReadyPreview ? ( <> {/* eslint-disable-next-line @next/next/no-img-element */} Mixer base setHasImageLoadError(true)} /> {/* eslint-disable-next-line @next/next/no-img-element */} Mixer overlay setHasImageLoadError(true)} style={{ mixBlendMode: previewState.blendMode, opacity: previewState.opacity / 100, transform: `translate(${previewState.offsetX}px, ${previewState.offsetY}px)`, }} /> ) : null} {previewState.status === "empty" && !showPreviewError ? (
Connect base and overlay images
) : null} {previewState.status === "partial" && !showPreviewError ? (
Waiting for second input
) : null} {showPreviewError ? (
Preview unavailable
) : null}
); }