modularize style and add propagating widgets

This commit is contained in:
2026-03-26 01:01:06 -07:00
parent 8e16f9f0b4
commit 2c3256fffc
17 changed files with 670 additions and 358 deletions

View File

@@ -23,42 +23,9 @@ import {
hasBlockingAutoRunInput,
} from './executionGraph';
// ── Constants ─────────────────────────────────────────────────────────
const DATA_TYPES = new Set([
'DATA_FIELD', 'IMAGE', 'LINE', 'MEASURE_TABLE', 'RECORD_TABLE', 'ANY_TABLE',
'COORD', 'STATS_SOURCE', 'CURSOR_SOURCE', 'VALUE_SOURCE', 'COLORMAP', 'SAVE_LAYER', 'FONT', 'FILE_PATH', 'DIRECTORY',
]);
const SOCKET_COMPATIBILITY = {
STATS_SOURCE: new Set(['DATA_FIELD', 'IMAGE', 'LINE', 'RECORD_TABLE']),
CURSOR_SOURCE: new Set(['DATA_FIELD', 'LINE']),
ANY_TABLE: new Set(['MEASURE_TABLE', 'RECORD_TABLE']),
VALUE_SOURCE: new Set(['FLOAT', 'MEASURE_TABLE']),
SAVE_LAYER: new Set(['DATA_FIELD', 'IMAGE']),
FLOAT: new Set(['INT']),
INT: new Set(['FLOAT']),
};
const TYPE_COLORS = {
DATA_FIELD: '#ff002f',
IMAGE: '#00ff08a0',
LINE: '#ffbe5c',
MEASURE_TABLE:'#35e2fd',
RECORD_TABLE:'#fbbf24',
ANY_TABLE: '#67e8f9',
COORD: '#e91ed1',
FLOAT: '#7dd3fc',
INT: '#38bdf8',
STATS_SOURCE:'#c084fc',
CURSOR_SOURCE:'#a78bfa',
VALUE_SOURCE:'#60a5fa',
COLORMAP: '#f472b6',
SAVE_LAYER: '#22c55e',
FONT: '#fb7185',
FILE_PATH: '#f59e0b',
DIRECTORY: '#f97316',
};
import {
DATA_TYPES, SOCKET_COMPATIBILITY, TYPE_COLORS, CAT_COLORS, CANVAS_COLORS,
} from './constants';
const NODE_TYPES = { custom: CustomNode };
@@ -378,7 +345,7 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
if (categories.length === 0) {
return (
<div className="context-menu" ref={menuRef} style={{ left: menuPos.x, top: menuPos.y }} onClick={(e) => e.stopPropagation()}>
<div className="context-item" style={{ color: '#64748b' }}>No compatible nodes</div>
<div className="context-item" style={{ color: 'var(--text-muted)' }}>No compatible nodes</div>
</div>
);
}
@@ -415,7 +382,7 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
{searchResults ? (
<div className="ctx-list">
{searchResults.length === 0 ? (
<div className="context-item" style={{ color: '#64748b' }}>No matches</div>
<div className="context-item" style={{ color: 'var(--text-muted)' }}>No matches</div>
) : (
searchResults.map(({ className, def }) => (
<div
@@ -655,7 +622,7 @@ function Flow() {
const onConnect = useCallback((params) => {
const type = getHandleType(params.sourceHandle);
const color = TYPE_COLORS[type] || '#999';
const color = TYPE_COLORS[type] || 'var(--fallback-type)';
setEdges((eds) => {
// Enforce single connection per input handle
@@ -864,7 +831,7 @@ function Flow() {
return type;
})();
const targetHandle = `input::${inputName}::${targetType}`;
const color = TYPE_COLORS[filterType] || '#999';
const color = TYPE_COLORS[filterType] || 'var(--fallback-type)';
setEdges((eds) => addEdge({
source: contextMenu.pendingNodeId,
sourceHandle: contextMenu.pendingHandleId,
@@ -879,7 +846,7 @@ function Flow() {
if (outputIdx !== -1) {
const outputType = def.output[outputIdx];
const sourceHandle = `output::${outputIdx}::${outputType}`;
const color = TYPE_COLORS[outputType] || '#999';
const color = TYPE_COLORS[outputType] || 'var(--fallback-type)';
setEdges((eds) => addEdge({
source: newNodeId,
sourceHandle,
@@ -1021,7 +988,7 @@ function Flow() {
const vp = getViewportForBounds(bounds, imageWidth, imageHeight, 0.5, 1, pad);
const blob = await captureWorkflowViewportBlob(viewportEl, {
backgroundColor: '#1a1a1a',
backgroundColor: CANVAS_COLORS.bgDeep,
width: imageWidth,
height: imageHeight,
style: {
@@ -1274,11 +1241,7 @@ function Flow() {
<MiniMap
nodeColor={(n) => {
const cat = n.data?.definition?.category;
const colors = {
io: '#37474f', filters: '#1a237e', level: '#1b5e20',
analysis: '#4a148c', particles: '#bf360c', display: '#212121',
};
return colors[cat] || '#333';
return CAT_COLORS[cat] || 'var(--fallback-cat)';
}}
/>
</ReactFlow>

View File

@@ -68,7 +68,7 @@ export default function CrossSectionOverlay({
<line
x1={`${x1 * 100}%`} y1={`${y1 * 100}%`}
x2={`${x2 * 100}%`} y2={`${y2 * 100}%`}
stroke="#ffd700" strokeWidth="2" strokeDasharray="6 3"
stroke="var(--marker)" strokeWidth="2" strokeDasharray="6 3"
/>
</svg>
)}
@@ -78,12 +78,12 @@ export default function CrossSectionOverlay({
className={`cs-marker ${aLocked ? 'cs-marker-locked' : ''}`}
style={{ left: `${x1 * 100}%`, top: `${y1 * 100}%` }}
onPointerDown={onPointerDown('p1')}
/>
>A</div>
<div
className={`cs-marker ${bLocked ? 'cs-marker-locked' : ''}`}
style={{ left: `${x2 * 100}%`, top: `${y2 * 100}%` }}
onPointerDown={onPointerDown('p2')}
/>
>B</div>
</div>
);
}

View File

@@ -8,43 +8,9 @@ const CropBoxOverlay = lazy(() => import('./CropBoxOverlay'));
const MaskPaintOverlay = lazy(() => import('./MaskPaintOverlay'));
const MarkupOverlay = lazy(() => import('./MarkupOverlay'));
// ── Constants ─────────────────────────────────────────────────────────
const DATA_TYPES = new Set([
'DATA_FIELD', 'IMAGE', 'LINE', 'MEASURE_TABLE', 'RECORD_TABLE', 'ANY_TABLE',
'COORD', 'STATS_SOURCE', 'CURSOR_SOURCE', 'VALUE_SOURCE', 'COLORMAP', 'SAVE_LAYER', 'FONT', 'FILE_PATH', 'DIRECTORY',
]);
const SOCKET_WIDGET_TYPES = new Set(['FLOAT', 'INT']);
const TYPE_COLORS = {
DATA_FIELD: '#3a7abf',
IMAGE: '#4caf50',
LINE: '#ff9800',
MEASURE_TABLE:'#35e2fd',
RECORD_TABLE:'#fbbf24',
ANY_TABLE: '#67e8f9',
COORD: '#e91e63',
FLOAT: '#7dd3fc',
INT: '#38bdf8',
STATS_SOURCE:'#c084fc',
CURSOR_SOURCE:'#a78bfa',
VALUE_SOURCE:'#60a5fa',
COLORMAP: '#f472b6',
SAVE_LAYER: '#22c55e',
FONT: '#fb7185',
FILE_PATH: '#f59e0b',
DIRECTORY: '#f97316',
};
const CAT_COLORS = {
io: '#37474f',
filters: '#1a237e',
modify: '#0f766e',
level: '#1b5e20',
analysis: '#4a148c',
particles:'#bf360c',
display: '#212121',
};
import {
DATA_TYPES, SOCKET_WIDGET_TYPES, TYPE_COLORS, CAT_COLORS,
} from './constants';
// ── Context (provided by App) ─────────────────────────────────────────
@@ -84,7 +50,7 @@ class PreviewBoundary extends React.Component {
}
return (
<div className="node-preview" style={{ color: '#94a3b8', padding: 8 }}>
<div className="node-preview" style={{ color: 'var(--text-secondary)', padding: 8 }}>
Preview unavailable.
</div>
);
@@ -440,6 +406,58 @@ function getConnectedOutputInfo(store, nodeId, inputName) {
};
}
/**
* Resolve live COORDPAIR values by walking edges back to upstream Coordinate
* nodes' widget values. Returns [x1, y1, x2, y2] (a flat array for stable
* equality comparison) or null if the chain can't be fully resolved.
*
* Uses store.nodes (the reactive array) rather than nodeLookup so that
* upstream widgetValues changes trigger re-renders.
*/
function resolveLiveCoordPair(store, nodeId, coordPairInputName) {
const nodes = store.nodes;
const edges = store.edges;
if (!nodes || !edges) return null;
const findNode = (nid) => nodes.find((n) => n.id === nid);
// 1. Find the edge feeding this node's COORDPAIR input
const cpEdge = edges.find(
(e) => e.target === nodeId && e.targetHandle?.startsWith(`input::${coordPairInputName}::`)
);
if (!cpEdge) return null;
const cpNode = findNode(cpEdge.source);
if (!cpNode) return null;
// If the source node is a CoordinatePair, walk one more level to Coordinate nodes
if (cpNode.data?.className === 'CoordinatePair') {
const resolveCoord = (inputName) => {
const edge = edges.find(
(e) => e.target === cpNode.id && e.targetHandle?.startsWith(`input::${inputName}::`)
);
if (!edge) return null;
const srcNode = findNode(edge.source);
if (!srcNode?.data?.widgetValues) return null;
const x = srcNode.data.widgetValues.x;
const y = srcNode.data.widgetValues.y;
return (x != null && y != null) ? [x, y] : null;
};
const a = resolveCoord('a');
const b = resolveCoord('b');
if (!a || !b) return null;
return [a[0], a[1], b[0], b[1]];
}
// If the source is a node with x1/y1/x2/y2 widgets (e.g. another CrossSection output)
const wv = cpNode.data?.widgetValues;
if (wv && wv.x1 != null && wv.y1 != null && wv.x2 != null && wv.y2 != null) {
return [wv.x1, wv.y1, wv.x2, wv.y2];
}
return null;
}
function getBasename(value) {
if (typeof value !== 'string') return '';
const trimmed = value.trim();
@@ -732,6 +750,29 @@ function CustomNode({ id, data }) {
useCallback((s) => getConnectedOutputInfo(s, id, 'path'), [id]),
);
// Find the COORDPAIR input name (if any) so we can resolve live upstream positions
const coordPairInputName = React.useMemo(() => {
const allInputs = { ...def.input.required, ...def.input.optional };
for (const [name, spec] of Object.entries(allInputs)) {
const type = Array.isArray(spec) ? spec[0] : spec;
if (type === 'COORDPAIR') return name;
}
return null;
}, [def]);
// Returns [x1, y1, x2, y2] or null — flat array for cheap equality check
const liveCoordPair = useStore(
useCallback(
(s) => coordPairInputName ? resolveLiveCoordPair(s, id, coordPairInputName) : null,
[id, coordPairInputName],
),
(a, b) => {
if (a === b) return true;
if (!a || !b) return false;
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
},
);
// Parse inputs into data handles and widgets
const required = def.input.required || {};
const optional = def.input.optional || {};
@@ -846,7 +887,7 @@ function CustomNode({ id, data }) {
slot: i,
}));
const catColor = CAT_COLORS[def.category] || '#333';
const catColor = CAT_COLORS[def.category] || 'var(--fallback-cat)';
const maxIORows = Math.max(renderedDataInputs.length, outputs.length);
const hasInteractiveLineOverlay = data.overlay?.kind === 'line_plot' && hiddenWidgets.has('x1');
const hasInteractiveOverlay = !!data.overlay && (
@@ -904,7 +945,7 @@ function CustomNode({ id, data }) {
position={Position.Left}
id={`input::${socketName}::${socketType}`}
className="typed-handle"
style={{ background: TYPE_COLORS[socketType] || '#999' }}
style={{ background: TYPE_COLORS[socketType] || 'var(--fallback-type)' }}
/>
);
})()}
@@ -939,7 +980,7 @@ function CustomNode({ id, data }) {
position={Position.Left}
id={`input::${inp.name}::${inp.type}`}
className="typed-handle"
style={{ background: TYPE_COLORS[inp.type] || '#999' }}
style={{ background: TYPE_COLORS[inp.type] || 'var(--fallback-type)' }}
/>
<span className="io-label">{inp.label || inp.name}</span>
{inlineWidgetsByInput.has(inp.name) && (
@@ -967,7 +1008,7 @@ function CustomNode({ id, data }) {
position={Position.Right}
id={`output::${out.slot}::${out.type}`}
className="typed-handle"
style={{ background: TYPE_COLORS[out.type] || '#999' }}
style={{ background: TYPE_COLORS[out.type] || 'var(--fallback-type)' }}
/>
</>
)}
@@ -1002,7 +1043,7 @@ function CustomNode({ id, data }) {
position={Position.Left}
id={`input::${w.name}::${w.socketType}`}
className="typed-handle"
style={{ background: TYPE_COLORS[w.socketType] || '#999' }}
style={{ background: TYPE_COLORS[w.socketType] || 'var(--fallback-type)' }}
/>
)}
<WidgetControl
@@ -1033,7 +1074,7 @@ function CustomNode({ id, data }) {
{/* Interactive 3D surface view */}
{data.meshData && (
<CollapsibleSection title="3D View" defaultOpen={true}>
<Suspense fallback={<div className="node-preview" style={{color:'#64748b',padding:4}}>Loading 3D...</div>}>
<Suspense fallback={<div className="node-preview" style={{color:'var(--text-muted)',padding:4}}>Loading 3D...</div>}>
<SurfaceView meshData={data.meshData} />
</Suspense>
</CollapsibleSection>
@@ -1068,12 +1109,12 @@ function CustomNode({ id, data }) {
{/* Interactive cross-section overlay */}
{hasInteractiveOverlay && (
<CollapsibleSection title={overlayTitle} defaultOpen={true}>
<Suspense fallback={<div className="node-preview" style={{color:'#64748b',padding:4}}>Loading...</div>}>
<Suspense fallback={<div className="node-preview" style={{color:'var(--text-muted)',padding:4}}>Loading...</div>}>
{data.overlay.kind === 'line_plot' ? (
<LinePlotOverlay
overlay={data.overlay}
x1={data.overlay.a_locked ? data.overlay.x1 : (data.widgetValues.x1 ?? data.overlay.x1)}
x2={data.overlay.b_locked ? data.overlay.x2 : (data.widgetValues.x2 ?? data.overlay.x2)}
x1={data.overlay.a_locked ? (liveCoordPair?.[0] ?? data.overlay.x1) : (data.widgetValues.x1 ?? data.overlay.x1)}
x2={data.overlay.b_locked ? (liveCoordPair?.[2] ?? data.overlay.x2) : (data.widgetValues.x2 ?? data.overlay.x2)}
aLocked={data.overlay.a_locked}
bLocked={data.overlay.b_locked}
nodeId={id}
@@ -1082,10 +1123,10 @@ function CustomNode({ id, data }) {
) : data.overlay.kind === 'crop_box' ? (
<CropBoxOverlay
image={data.overlay.image}
x1={data.overlay.a_locked ? data.overlay.x1 : (data.widgetValues.x1 ?? data.overlay.x1)}
y1={data.overlay.a_locked ? data.overlay.y1 : (data.widgetValues.y1 ?? data.overlay.y1)}
x2={data.overlay.b_locked ? data.overlay.x2 : (data.widgetValues.x2 ?? data.overlay.x2)}
y2={data.overlay.b_locked ? data.overlay.y2 : (data.widgetValues.y2 ?? data.overlay.y2)}
x1={data.overlay.a_locked ? (liveCoordPair?.[0] ?? data.overlay.x1) : (data.widgetValues.x1 ?? data.overlay.x1)}
y1={data.overlay.a_locked ? (liveCoordPair?.[1] ?? data.overlay.y1) : (data.widgetValues.y1 ?? data.overlay.y1)}
x2={data.overlay.b_locked ? (liveCoordPair?.[2] ?? data.overlay.x2) : (data.widgetValues.x2 ?? data.overlay.x2)}
y2={data.overlay.b_locked ? (liveCoordPair?.[3] ?? data.overlay.y2) : (data.widgetValues.y2 ?? data.overlay.y2)}
aLocked={data.overlay.a_locked}
bLocked={data.overlay.b_locked}
nodeId={id}
@@ -1094,10 +1135,10 @@ function CustomNode({ id, data }) {
) : data.overlay.kind === 'cursor_points' ? (
<CrossSectionOverlay
image={data.overlay.image}
x1={data.overlay.a_locked ? data.overlay.x1 : (data.widgetValues.x1 ?? data.overlay.x1)}
y1={data.overlay.a_locked ? data.overlay.y1 : (data.widgetValues.y1 ?? data.overlay.y1)}
x2={data.overlay.b_locked ? data.overlay.x2 : (data.widgetValues.x2 ?? data.overlay.x2)}
y2={data.overlay.b_locked ? data.overlay.y2 : (data.widgetValues.y2 ?? data.overlay.y2)}
x1={data.overlay.a_locked ? (liveCoordPair?.[0] ?? data.overlay.x1) : (data.widgetValues.x1 ?? data.overlay.x1)}
y1={data.overlay.a_locked ? (liveCoordPair?.[1] ?? data.overlay.y1) : (data.widgetValues.y1 ?? data.overlay.y1)}
x2={data.overlay.b_locked ? (liveCoordPair?.[2] ?? data.overlay.x2) : (data.widgetValues.x2 ?? data.overlay.x2)}
y2={data.overlay.b_locked ? (liveCoordPair?.[3] ?? data.overlay.y2) : (data.widgetValues.y2 ?? data.overlay.y2)}
aLocked={data.overlay.a_locked}
bLocked={data.overlay.b_locked}
nodeId={id}
@@ -1365,7 +1406,7 @@ function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFile
if (type === 'STRING' && opts?.color_picker) {
const normalized = typeof val === 'string' && /^#[0-9a-fA-F]{6}$/.test(val)
? val
: '#ffd54f';
: 'var(--shape-default)';
return (
<>
{!hideLabel && <label>{label}</label>}

View File

@@ -214,14 +214,14 @@ export default function LinePlotOverlay({
onLostPointerCapture={onPointerUp}
>
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} className="lineplot-svg">
<rect x="0" y="0" width={width} height={height} fill="#0f172a" />
<rect x="0" y="0" width={width} height={height} fill="var(--bg-deep)" />
{xTicks.map((tick) => {
const x = scaleX(tick);
return (
<g key={`x-${tick}`}>
<line x1={x} y1={plotTop} x2={x} y2={plotTop + plotHeight} stroke="#334155" strokeWidth={gridStroke} opacity="0.45" />
<text x={x} y={height - 10} textAnchor="middle" fontSize="11" fill="#94a3b8">
<line x1={x} y1={plotTop} x2={x} y2={plotTop + plotHeight} stroke="var(--border-default)" strokeWidth={gridStroke} opacity="0.45" />
<text x={x} y={height - 10} textAnchor="middle" fontSize="11" fill="var(--text-secondary)">
{formatTick(tick)}
</text>
</g>
@@ -232,22 +232,22 @@ export default function LinePlotOverlay({
const y = scaleY(tick);
return (
<g key={`y-${tick}`}>
<line x1={plotLeft} y1={y} x2={plotLeft + plotWidth} y2={y} stroke="#334155" strokeWidth={gridStroke} opacity="0.45" />
<text x={plotLeft - 10} y={y + 4} textAnchor="end" fontSize="11" fill="#94a3b8">
<line x1={plotLeft} y1={y} x2={plotLeft + plotWidth} y2={y} stroke="var(--border-default)" strokeWidth={gridStroke} opacity="0.45" />
<text x={plotLeft - 10} y={y + 4} textAnchor="end" fontSize="11" fill="var(--text-secondary)">
{formatTick(tick)}
</text>
</g>
);
})}
<rect x={plotLeft} y={plotTop} width={plotWidth} height={plotHeight} fill="none" stroke="#334155" strokeWidth={gridStroke + 0.3} />
<path d={path} fill="none" stroke="#ff9800" strokeWidth={plotStroke} strokeLinecap="round" strokeLinejoin="round" />
<rect x={plotLeft} y={plotTop} width={plotWidth} height={plotHeight} fill="none" stroke="var(--border-default)" strokeWidth={gridStroke + 0.3} />
<path d={path} fill="none" stroke="var(--plot-line)" strokeWidth={plotStroke} strokeLinecap="round" strokeLinejoin="round" />
{interactive && (
<>
<line x1={cursorA.x} y1={plotTop} x2={cursorA.x} y2={plotTop + plotHeight} stroke="#ffd700" strokeWidth={cursorStroke} strokeDasharray="10 6" opacity="0.95" />
<line x1={cursorB.x} y1={plotTop} x2={cursorB.x} y2={plotTop + plotHeight} stroke="#ffd700" strokeWidth={cursorStroke} strokeDasharray="10 6" opacity="0.95" />
<line x1={cursorA.x} y1={cursorA.y} x2={cursorB.x} y2={cursorB.y} stroke="#90caf9" strokeWidth={measureStroke} opacity="0.95" />
<line x1={cursorA.x} y1={plotTop} x2={cursorA.x} y2={plotTop + plotHeight} stroke="var(--marker)" strokeWidth={cursorStroke} strokeDasharray="10 6" opacity="0.95" />
<line x1={cursorB.x} y1={plotTop} x2={cursorB.x} y2={plotTop + plotHeight} stroke="var(--marker)" strokeWidth={cursorStroke} strokeDasharray="10 6" opacity="0.95" />
<line x1={cursorA.x} y1={cursorA.y} x2={cursorB.x} y2={cursorB.y} stroke="var(--accent-light)" strokeWidth={measureStroke} opacity="0.95" />
<circle
cx={cursorA.x}

View File

@@ -6,7 +6,9 @@ function clampFraction(value) {
return Math.max(0, Math.min(1, numeric));
}
function sanitizeColor(color, fallback = '#ffd54f') {
const SHAPE_DEFAULT_COLOR = '#ffd54f';
function sanitizeColor(color, fallback = SHAPE_DEFAULT_COLOR) {
if (typeof color !== 'string') return fallback;
const value = color.trim();
return /^#[0-9a-fA-F]{6}$/.test(value) ? value.toLowerCase() : fallback;

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { CANVAS_COLORS } from './constants';
function clampFraction(value) {
const numeric = Number(value);
@@ -52,8 +53,8 @@ function drawStroke(ctx, stroke, width, height, imageWidth, imageHeight, styles
ctx.save();
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = styles.strokeStyle || '#ffffff';
ctx.fillStyle = styles.fillStyle || '#ffffff';
ctx.strokeStyle = styles.strokeStyle || CANVAS_COLORS.maskStroke;
ctx.fillStyle = styles.fillStyle || CANVAS_COLORS.maskStroke;
ctx.lineWidth = lineWidth;
const points = stroke.points.map((point) => ({
@@ -160,7 +161,7 @@ export default function MaskPaintOverlay({
cssHeight,
imageWidth,
imageHeight,
{ strokeStyle: '#ffffff', fillStyle: '#ffffff' },
{ strokeStyle: CANVAS_COLORS.maskStroke, fillStyle: CANVAS_COLORS.maskStroke },
);
for (const stroke of committedStrokes) {
@@ -172,7 +173,7 @@ export default function MaskPaintOverlay({
ctx.drawImage(maskCanvas, 0, 0);
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = 'rgba(255, 59, 59, 0.16)';
ctx.fillStyle = CANVAS_COLORS.maskOverlay;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-over';
}, [imageHeight, imageWidth]);

58
frontend/src/constants.js Normal file
View File

@@ -0,0 +1,58 @@
// ── Shared type & color constants ─────────────────────────────────────
export const DATA_TYPES = new Set([
'DATA_FIELD', 'IMAGE', 'LINE', 'MEASURE_TABLE', 'RECORD_TABLE', 'ANY_TABLE',
'COORD', 'STATS_SOURCE', 'CURSOR_SOURCE', 'VALUE_SOURCE', 'COLORMAP',
'SAVE_LAYER', 'FONT', 'FILE_PATH', 'DIRECTORY', 'COORDPAIR',
]);
export const SOCKET_WIDGET_TYPES = new Set(['FLOAT', 'INT']);
export const TYPE_COLORS = {
DATA_FIELD: '#3a7abf',
IMAGE: '#00ff08a0',
LINE: '#ffbe5c',
MEASURE_TABLE: '#35e2fd',
RECORD_TABLE: '#fbbf24',
ANY_TABLE: '#67e8f9',
COORD: '#e91ed1',
COORDPAIR: '#5c7cb8',
FLOAT: '#ab3197',
INT: '#38bdf8',
STATS_SOURCE: '#c084fc',
CURSOR_SOURCE: '#a78bfa',
VALUE_SOURCE: '#60a5fa',
COLORMAP: '#f472b6',
SAVE_LAYER: '#22c55e',
FONT: '#fb7185',
FILE_PATH: '#f59e0b',
DIRECTORY: '#f97316',
};
export const CAT_COLORS = {
io: '#37474f',
filters: '#1a237e',
modify: '#0f766e',
level: '#1b5e20',
analysis: '#4a148c',
particles: '#bf360c',
display: '#212121',
};
export const SOCKET_COMPATIBILITY = {
STATS_SOURCE: new Set(['DATA_FIELD', 'IMAGE', 'LINE', 'RECORD_TABLE']),
CURSOR_SOURCE: new Set(['DATA_FIELD', 'LINE']),
ANY_TABLE: new Set(['MEASURE_TABLE', 'RECORD_TABLE']),
VALUE_SOURCE: new Set(['FLOAT', 'MEASURE_TABLE']),
SAVE_LAYER: new Set(['DATA_FIELD', 'IMAGE']),
FLOAT: new Set(['INT']),
INT: new Set(['FLOAT']),
LINE: new Set(['COORDPAIR']),
};
// Colors used in Canvas 2D / toBlob contexts where CSS var() is unavailable.
export const CANVAS_COLORS = {
bgDeep: '#0f172a',
maskStroke: '#ffffff',
maskOverlay: 'rgba(255, 59, 59, 0.16)',
};

View File

@@ -1,7 +1,4 @@
const DATA_TYPES = new Set([
'DATA_FIELD', 'IMAGE', 'LINE', 'MEASURE_TABLE', 'RECORD_TABLE', 'ANY_TABLE',
'COORD', 'STATS_SOURCE', 'CURSOR_SOURCE', 'VALUE_SOURCE', 'COLORMAP', 'SAVE_LAYER', 'FONT', 'FILE_PATH', 'DIRECTORY',
]);
import { DATA_TYPES } from './constants';
function getInputName(handleId) {
return handleId.split('::')[1];

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
import { toBlob } from 'html-to-image';
import { CANVAS_COLORS } from './constants';
export const OVERLAY_CAPTURE_SELECTORS = [
'.lineplot-overlay',
@@ -115,7 +116,7 @@ async function renderElementToDataUrl(el, toBlobImpl) {
const blob = await toBlobImpl(el, {
width,
height,
backgroundColor: '#0f172a',
backgroundColor: CANVAS_COLORS.bgDeep,
style: {
width: `${width}px`,
height: `${height}px`,