initial migration to TS
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
measureAngleDegrees,
|
||||
moveAngleWidget,
|
||||
round3,
|
||||
} from './angleMeasureGeometry.js';
|
||||
} from './angleMeasureGeometry';
|
||||
|
||||
function clamp01(value) {
|
||||
return Math.max(0, Math.min(1, Number(value) || 0));
|
||||
@@ -20,7 +20,7 @@ import { hydrateWorkflowState } from './workflowHydration';
|
||||
import useUndoRedo from './useUndoRedo';
|
||||
import { packWorkflow, unpackWorkflow } from './workflowPacking';
|
||||
import { serializeWorkflowState } from './workflowSerialization';
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.js';
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy';
|
||||
import {
|
||||
buildNodeClipboardPayload,
|
||||
buildNodeClipboardPayloadForIds,
|
||||
@@ -38,8 +38,8 @@ import {
|
||||
beginTrackedNodeRequest,
|
||||
isTrackedNodeRequestCurrent,
|
||||
resolveLoadNodeChannelPath,
|
||||
} from './loadNodeOutputs.js';
|
||||
import { buildDefaultWidgetValues } from './nodeWidgetDefaults.js';
|
||||
} from './loadNodeOutputs';
|
||||
import { buildDefaultWidgetValues } from './nodeWidgetDefaults';
|
||||
import {
|
||||
getHandleType,
|
||||
getInputName,
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
outputTypeCanConnectToTarget,
|
||||
resolveOutputTypeForTarget,
|
||||
checkConnectionValid,
|
||||
} from './connectionUtils.js';
|
||||
} from './connectionUtils';
|
||||
|
||||
import {
|
||||
getSpecTypeAndOptions,
|
||||
@@ -2624,7 +2624,7 @@ function Flow() {
|
||||
|
||||
let nextNodes = currentNodes;
|
||||
let changed = false;
|
||||
let structureChanged = false;
|
||||
const structureChanged = false;
|
||||
|
||||
nextNodes = nextNodes.map((candidate) => {
|
||||
const candidateId = String(candidate.id);
|
||||
@@ -18,9 +18,9 @@ import TextNoteNode from './TextNoteNode';
|
||||
import {
|
||||
getSpecTypeAndOptions, isDataSocketSpec, SOCKET_WIDGET_TYPES, TYPE_COLORS, CAT_COLORS,
|
||||
} from './constants';
|
||||
import { getGroupMinimumSize } from './groupSizing.js';
|
||||
import { buildCombinedInputNameByWidgetName, formatUiLabel } from './nodeWidgetLayout.js';
|
||||
import { applySIPrefix, formatNumericCell, formatTableRowCell, getTableColumns, parseNumberWithUnit } from './valueFormatting.js';
|
||||
import { getGroupMinimumSize } from './groupSizing';
|
||||
import { buildCombinedInputNameByWidgetName, formatUiLabel } from './nodeWidgetLayout';
|
||||
import { applySIPrefix, formatNumericCell, formatTableRowCell, getTableColumns, parseNumberWithUnit } from './valueFormatting';
|
||||
|
||||
// ── Context (provided by App) ─────────────────────────────────────────
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
parseMarkupShapes,
|
||||
sanitizeMarkupColor,
|
||||
sanitizeMarkupShape,
|
||||
} from './markupShapeGeometry.js';
|
||||
} from './markupShapeGeometry';
|
||||
|
||||
function clampFraction(value) {
|
||||
const numeric = Number(value);
|
||||
@@ -1,31 +1,32 @@
|
||||
const EXCLUDED_CANVAS_TARGETS = '.context-menu, .react-flow__node, .react-flow__edge, .react-flow__controls, .react-flow__minimap, .surface-view-container';
|
||||
const CANVAS_AREA_TARGETS = '.react-flow, .react-flow__renderer, .react-flow__viewport, .react-flow__pane, .react-flow__background, .react-flow__selectionpane';
|
||||
|
||||
function getTargetElement(target) {
|
||||
function getTargetElement(target: EventTarget | null): Element | null {
|
||||
if (!target) return null;
|
||||
if (typeof target.closest === 'function') return target;
|
||||
if (target.parentElement && typeof target.parentElement.closest === 'function') {
|
||||
return target.parentElement;
|
||||
if (typeof (target as Element).closest === 'function') return target as Element;
|
||||
const parent = (target as Node).parentElement;
|
||||
if (parent && typeof parent.closest === 'function') {
|
||||
return parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function supportsClosest(target) {
|
||||
function supportsClosest(target: EventTarget | null): boolean {
|
||||
return !!getTargetElement(target);
|
||||
}
|
||||
|
||||
function matchesClosest(target, selector) {
|
||||
function matchesClosest(target: EventTarget | null, selector: string): boolean {
|
||||
const element = getTargetElement(target);
|
||||
return !!element && element.closest(selector) !== null;
|
||||
}
|
||||
|
||||
export function isEditableInteractionTarget(target) {
|
||||
export function isEditableInteractionTarget(target: EventTarget | null): boolean {
|
||||
if (!supportsClosest(target)) return false;
|
||||
if (matchesClosest(target, 'input, textarea, select')) return true;
|
||||
return matchesClosest(target, '[contenteditable="true"]');
|
||||
}
|
||||
|
||||
export function canStartCanvasRightDragZoomTarget(target) {
|
||||
export function canStartCanvasRightDragZoomTarget(target: EventTarget | null): boolean {
|
||||
if (!supportsClosest(target)) return false;
|
||||
if (isEditableInteractionTarget(target)) return false;
|
||||
if (matchesClosest(target, EXCLUDED_CANVAS_TARGETS)) {
|
||||
@@ -34,7 +35,7 @@ export function canStartCanvasRightDragZoomTarget(target) {
|
||||
return matchesClosest(target, CANVAS_AREA_TARGETS);
|
||||
}
|
||||
|
||||
export function canOpenCanvasContextMenuTarget(target) {
|
||||
export function canOpenCanvasContextMenuTarget(target: EventTarget | null): boolean {
|
||||
if (!supportsClosest(target)) return false;
|
||||
if (isEditableInteractionTarget(target)) return false;
|
||||
if (matchesClosest(target, EXCLUDED_CANVAS_TARGETS)) {
|
||||
@@ -43,7 +44,7 @@ export function canOpenCanvasContextMenuTarget(target) {
|
||||
return matchesClosest(target, CANVAS_AREA_TARGETS);
|
||||
}
|
||||
|
||||
export function isSecondaryCanvasContextEvent(event) {
|
||||
export function isSecondaryCanvasContextEvent(event: MouseEvent | null): boolean {
|
||||
if (!event || typeof event.button !== 'number') return false;
|
||||
return event.button === 2 || (event.button === 0 && !!event.ctrlKey);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// ── Connection utility functions ───────────────────────────────────────
|
||||
// Pure functions extracted from App.jsx so they can be independently tested.
|
||||
|
||||
import { socketSpecAcceptsType } from './constants.js';
|
||||
import { socketSpecAcceptsType } from './constants.ts';
|
||||
|
||||
// ── Handle ID helpers ─────────────────────────────────────────────────
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { InputSpec, InputOptions } from './types.ts';
|
||||
|
||||
// ── Shared type & color constants ─────────────────────────────────────
|
||||
|
||||
export const DATA_TYPES = new Set([
|
||||
@@ -8,7 +10,7 @@ export const DATA_TYPES = new Set([
|
||||
|
||||
export const SOCKET_WIDGET_TYPES = new Set(['FLOAT', 'INT']);
|
||||
|
||||
export const TYPE_COLORS = {
|
||||
export const TYPE_COLORS: Record<string, string> = {
|
||||
DATA_FIELD: '#3a7abf',
|
||||
IMAGE: '#00ff08a0',
|
||||
LINE: '#ffbe5c',
|
||||
@@ -26,7 +28,7 @@ export const TYPE_COLORS = {
|
||||
DIRECTORY: '#f97316',
|
||||
};
|
||||
|
||||
export const CAT_COLORS = {
|
||||
export const CAT_COLORS: Record<string, string> = {
|
||||
Input: '#37474f',
|
||||
Display: '#212121',
|
||||
Overlay: '#0f766e',
|
||||
@@ -39,34 +41,34 @@ export const CAT_COLORS = {
|
||||
Grains: '#bf360c',
|
||||
};
|
||||
|
||||
export const SOCKET_COMPATIBILITY = {
|
||||
export const SOCKET_COMPATIBILITY: Record<string, Set<string>> = {
|
||||
FLOAT: new Set(['INT']),
|
||||
INT: new Set(['FLOAT']),
|
||||
LINE: new Set(['COORDPAIR']),
|
||||
};
|
||||
|
||||
const EMPTY_SOCKET_TYPE_SET = new Set();
|
||||
const EMPTY_SOCKET_TYPE_SET: Set<string> = new Set();
|
||||
|
||||
export function getSpecTypeAndOptions(spec) {
|
||||
export function getSpecTypeAndOptions(spec: InputSpec): [string | string[], InputOptions] {
|
||||
if (Array.isArray(spec)) {
|
||||
return [spec[0], spec[1] || {}];
|
||||
return [spec[0], (spec[1] || {}) as InputOptions];
|
||||
}
|
||||
return [spec, {}];
|
||||
}
|
||||
|
||||
export function isDataSocketType(type) {
|
||||
export function isDataSocketType(type: unknown): boolean {
|
||||
return typeof type === 'string' && DATA_TYPES.has(type);
|
||||
}
|
||||
|
||||
export function isDataSocketSpec(spec) {
|
||||
export function isDataSocketSpec(spec: InputSpec): boolean {
|
||||
const [type] = getSpecTypeAndOptions(spec);
|
||||
return isDataSocketType(type);
|
||||
}
|
||||
|
||||
export function getAcceptedSocketTypes(specOrType) {
|
||||
export function getAcceptedSocketTypes(specOrType: InputSpec | string): Set<string> {
|
||||
const [type, opts] = Array.isArray(specOrType)
|
||||
? getSpecTypeAndOptions(specOrType)
|
||||
: [specOrType, {}];
|
||||
? getSpecTypeAndOptions(specOrType as InputSpec)
|
||||
: [specOrType, {} as InputOptions];
|
||||
if (typeof type !== 'string') {
|
||||
return EMPTY_SOCKET_TYPE_SET;
|
||||
}
|
||||
@@ -89,7 +91,7 @@ export function getAcceptedSocketTypes(specOrType) {
|
||||
return accepted;
|
||||
}
|
||||
|
||||
export function socketSpecAcceptsType(sourceType, targetSpecOrType) {
|
||||
export function socketSpecAcceptsType(sourceType: string, targetSpecOrType: InputSpec | string): boolean {
|
||||
if (typeof sourceType !== 'string' || !sourceType) return false;
|
||||
return getAcceptedSocketTypes(targetSpecOrType).has(sourceType);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { extractWorkflow } from './pngMetadata.js';
|
||||
import { extractWorkflow } from './pngMetadata.ts';
|
||||
|
||||
const DEFAULT_WORKFLOW_CANDIDATES = [
|
||||
{ path: '/default-workflow.json', type: 'json' },
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.js';
|
||||
import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.ts';
|
||||
|
||||
const OMITTED_WIDGET_INPUTS_BY_CLASS = {
|
||||
View3D: new Set([
|
||||
@@ -1,6 +1,18 @@
|
||||
export const GROUP_DRAG_RELEASE_DISTANCE = 18;
|
||||
|
||||
export function getPointDistanceOutsideRect(rect, point) {
|
||||
interface Rect {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function getPointDistanceOutsideRect(rect: Rect | null, point: Point | null): number {
|
||||
if (!rect || !point) return Infinity;
|
||||
|
||||
const dx = point.x < rect.left
|
||||
@@ -13,6 +25,6 @@ export function getPointDistanceOutsideRect(rect, point) {
|
||||
return Math.hypot(dx, dy);
|
||||
}
|
||||
|
||||
export function shouldReleaseFromGroup(rect, point, threshold = GROUP_DRAG_RELEASE_DISTANCE) {
|
||||
export function shouldReleaseFromGroup(rect: Rect | null, point: Point | null, threshold = GROUP_DRAG_RELEASE_DISTANCE): boolean {
|
||||
return getPointDistanceOutsideRect(rect, point) >= threshold;
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
const DEFAULT_CHILD_WIDTH = 200;
|
||||
const DEFAULT_CHILD_HEIGHT = 120;
|
||||
|
||||
function getNodeSize(node, axis) {
|
||||
interface SizableNode {
|
||||
position?: { x: number; y: number };
|
||||
measured?: { width?: number; height?: number };
|
||||
width?: number;
|
||||
height?: number;
|
||||
style?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
function getNodeSize(node: SizableNode | null | undefined, axis: 'width' | 'height'): number {
|
||||
const fallback = axis === 'width' ? DEFAULT_CHILD_WIDTH : DEFAULT_CHILD_HEIGHT;
|
||||
const measured = Number(node?.measured?.[axis]);
|
||||
if (Number.isFinite(measured) && measured > 0) return measured;
|
||||
@@ -12,7 +20,7 @@ function getNodeSize(node, axis) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function getGroupMinimumSize(memberNodes, {
|
||||
export function getGroupMinimumSize(memberNodes: SizableNode[] | null | undefined, {
|
||||
minWidth = 260,
|
||||
minHeight = 180,
|
||||
paddingX = 24,
|
||||
@@ -1,9 +1,9 @@
|
||||
export function resolveLoadNodeChannelPath({
|
||||
explicitPath = null,
|
||||
resolvedPathInput = null,
|
||||
explicitPath = null as string | null,
|
||||
resolvedPathInput = null as string | null,
|
||||
className = '',
|
||||
widgetValues = {},
|
||||
} = {}) {
|
||||
widgetValues = {} as Record<string, unknown>,
|
||||
} = {}): string {
|
||||
if (typeof explicitPath === 'string' && explicitPath) {
|
||||
return explicitPath;
|
||||
}
|
||||
@@ -19,12 +19,12 @@ export function resolveLoadNodeChannelPath({
|
||||
return '';
|
||||
}
|
||||
|
||||
export function beginTrackedNodeRequest(requestVersions, nodeId) {
|
||||
export function beginTrackedNodeRequest(requestVersions: Map<string, number>, nodeId: string): number {
|
||||
const nextVersion = (requestVersions.get(nodeId) || 0) + 1;
|
||||
requestVersions.set(nodeId, nextVersion);
|
||||
return nextVersion;
|
||||
}
|
||||
|
||||
export function isTrackedNodeRequestCurrent(requestVersions, nodeId, version) {
|
||||
export function isTrackedNodeRequestCurrent(requestVersions: Map<string, number>, nodeId: string, version: number): boolean {
|
||||
return requestVersions.get(nodeId) === version;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.js';
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.ts';
|
||||
|
||||
export const NODE_CLIPBOARD_KIND = 'tono/node-selection';
|
||||
export const NODE_CLIPBOARD_MIME = 'application/x-tono-node-selection';
|
||||
@@ -1,12 +1,18 @@
|
||||
export function sortNodesForParentOrder(nodes) {
|
||||
interface NodeLike {
|
||||
id: string | number;
|
||||
parentId?: string | number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export function sortNodesForParentOrder<T extends NodeLike>(nodes: T[]): T[] {
|
||||
const list = Array.isArray(nodes) ? nodes.filter(Boolean) : [];
|
||||
const entries = list.map((node) => ({ id: String(node.id), node }));
|
||||
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
||||
const visiting = new Set();
|
||||
const visited = new Set();
|
||||
const ordered = [];
|
||||
const visiting = new Set<string>();
|
||||
const visited = new Set<string>();
|
||||
const ordered: T[] = [];
|
||||
|
||||
function visit(entry) {
|
||||
function visit(entry: { id: string; node: T } | undefined) {
|
||||
if (!entry) return;
|
||||
const { id, node } = entry;
|
||||
if (visited.has(id) || visiting.has(id)) return;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.js';
|
||||
import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.ts';
|
||||
|
||||
export function getDefaultWidgetValue(spec) {
|
||||
const [type, opts] = getSpecTypeAndOptions(spec);
|
||||
@@ -1,4 +1,4 @@
|
||||
export function formatUiLabel(text) {
|
||||
export function formatUiLabel(text: unknown): string {
|
||||
return String(text ?? '')
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
@@ -6,7 +6,7 @@ export function formatUiLabel(text) {
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function normalizeInputNames(raw) {
|
||||
function normalizeInputNames(raw: unknown): string[] {
|
||||
if (!raw) return [];
|
||||
return (Array.isArray(raw) ? raw : [raw])
|
||||
.map((value) => String(value))
|
||||
@@ -1,4 +1,4 @@
|
||||
export function sanitizeRuntimeValuesForPersistence(className, runtimeValues) {
|
||||
export function sanitizeRuntimeValuesForPersistence(className: string | undefined, runtimeValues: Record<string, unknown> | undefined): Record<string, unknown> {
|
||||
if (!runtimeValues || typeof runtimeValues !== 'object' || Array.isArray(runtimeValues)) {
|
||||
return {};
|
||||
}
|
||||
235
frontend/src/types.ts
Normal file
235
frontend/src/types.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import type { Node, Edge } from '@xyflow/react';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
// ── Input Specifications ─────────────────────────────────────────────
|
||||
|
||||
export interface InputOptions {
|
||||
label?: string;
|
||||
hidden?: boolean;
|
||||
socket_only?: boolean;
|
||||
accepted_types?: string[];
|
||||
default?: unknown;
|
||||
placeholder?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
slider?: boolean;
|
||||
min_widget?: string;
|
||||
max_widget?: string;
|
||||
text_input?: boolean;
|
||||
color_picker?: boolean;
|
||||
colormap_stops?: boolean;
|
||||
set_widgets?: Record<string, unknown>;
|
||||
show_when_source_type?: Record<string, string[]>;
|
||||
show_when_widget_value?: Record<string, unknown[]>;
|
||||
show_when_input_visible?: string | string[];
|
||||
hide_when_input_connected?: string | string[];
|
||||
choices_by_source_type?: Record<string, string[]>;
|
||||
choices_from_table_input?: string;
|
||||
choices_from_measure_input?: string;
|
||||
inline_with_input?: string | [string];
|
||||
source_type_input?: string;
|
||||
placement?: 'top';
|
||||
}
|
||||
|
||||
/** An input spec is either a bare type string, or a [type, opts] tuple. */
|
||||
export type InputSpec = string | [type: string | string[], opts?: InputOptions];
|
||||
|
||||
// ── Node Definition (from GET /nodes) ────────────────────────────────
|
||||
|
||||
export interface NodeDefinition {
|
||||
input: {
|
||||
required: Record<string, InputSpec>;
|
||||
optional: Record<string, InputSpec>;
|
||||
};
|
||||
output: string[];
|
||||
output_name: string[];
|
||||
output_paths?: string[];
|
||||
category: string;
|
||||
manual_trigger?: boolean;
|
||||
}
|
||||
|
||||
export type NodeDefsRegistry = Record<string, NodeDefinition>;
|
||||
|
||||
// ── Overlay Types ────────────────────────────────────────────────────
|
||||
|
||||
export interface OverlayData {
|
||||
kind: string;
|
||||
image?: string;
|
||||
image_width?: number;
|
||||
image_height?: number;
|
||||
x1?: number;
|
||||
y1?: number;
|
||||
x2?: number;
|
||||
y2?: number;
|
||||
xm?: number;
|
||||
ym?: number;
|
||||
a_locked?: boolean;
|
||||
b_locked?: boolean;
|
||||
section_title?: string;
|
||||
line?: number[];
|
||||
shape?: string;
|
||||
stroke_color?: string;
|
||||
stroke_width?: number;
|
||||
angle_deg?: number;
|
||||
color?: string;
|
||||
label_dx?: number;
|
||||
label_dy?: number;
|
||||
line_thickness?: number;
|
||||
histogram?: unknown;
|
||||
}
|
||||
|
||||
// ── Preview Image Types ──────────────────────────────────────────────
|
||||
|
||||
export interface PreviewPanel {
|
||||
kind: 'image' | 'line_plot';
|
||||
title?: string;
|
||||
image?: string;
|
||||
line?: number[];
|
||||
fallback_image?: string;
|
||||
}
|
||||
|
||||
export interface PreviewPayload {
|
||||
kind: 'image' | 'line_plot' | 'layer_gallery' | 'panels';
|
||||
image?: string;
|
||||
line?: number[];
|
||||
layers?: Array<{ name?: string; image: string }>;
|
||||
fallback_image?: string;
|
||||
panels?: PreviewPanel[];
|
||||
}
|
||||
|
||||
export type PreviewImage = string | PreviewPayload;
|
||||
|
||||
// ── Node Data (attached to each ReactFlow node) ──────────────────────
|
||||
|
||||
export interface GroupProxy {
|
||||
handleId: string;
|
||||
type: string;
|
||||
label: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface NodeData extends Record<string, unknown> {
|
||||
label: string;
|
||||
className: string;
|
||||
definition?: NodeDefinition | null;
|
||||
widgetValues: Record<string, unknown>;
|
||||
runtimeValues?: Record<string, unknown>;
|
||||
|
||||
// Execution results
|
||||
previewImage?: PreviewImage | null;
|
||||
tableRows?: Array<Record<string, unknown>> | null;
|
||||
scalarValue?: number | { value: number | string; unit?: string } | null;
|
||||
meshData?: unknown;
|
||||
overlay?: OverlayData | null;
|
||||
|
||||
// Status
|
||||
error?: string | null;
|
||||
warning?: string | null;
|
||||
processingTimeMs?: number | null;
|
||||
|
||||
// Group node fields
|
||||
proxyInputs?: GroupProxy[];
|
||||
proxyOutputs?: GroupProxy[];
|
||||
childCount?: number;
|
||||
collapsed?: boolean;
|
||||
expandedSize?: { width: number; height: number };
|
||||
|
||||
// Serialization extras
|
||||
extraData?: Record<string, unknown>;
|
||||
output?: string[];
|
||||
output_name?: string[];
|
||||
}
|
||||
|
||||
// ── ReactFlow Node & Edge ────────────────────────────────────────────
|
||||
|
||||
export type TonoNode = Node<NodeData, 'custom'>;
|
||||
export type TonoEdge = Edge<{
|
||||
groupProxyOwner?: string;
|
||||
groupProxyOriginal?: {
|
||||
source?: string;
|
||||
sourceHandle?: string;
|
||||
target?: string;
|
||||
targetHandle?: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
// ── Serialized Workflow ──────────────────────────────────────────────
|
||||
|
||||
export interface SerializedNode {
|
||||
id: string;
|
||||
type?: string;
|
||||
position: { x: number; y: number };
|
||||
width?: number;
|
||||
height?: number;
|
||||
className?: string;
|
||||
parentId?: string;
|
||||
extent?: [[number, number], [number, number]];
|
||||
hidden?: boolean;
|
||||
style?: CSSProperties;
|
||||
dragHandle?: string;
|
||||
data: {
|
||||
label: string;
|
||||
className: string;
|
||||
widgetValues: Record<string, unknown>;
|
||||
runtimeValues?: Record<string, unknown>;
|
||||
extraData?: Record<string, unknown>;
|
||||
output?: string[];
|
||||
output_name?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SerializedEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
sourceHandle?: string;
|
||||
target: string;
|
||||
targetHandle?: string;
|
||||
style?: CSSProperties;
|
||||
hidden?: boolean;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SerializedWorkflow {
|
||||
version: number;
|
||||
nodes: SerializedNode[];
|
||||
edges: SerializedEdge[];
|
||||
packed?: boolean;
|
||||
packedFiles?: Record<string, { filename: string; data: string }>;
|
||||
}
|
||||
|
||||
// ── WebSocket Messages ───────────────────────────────────────────────
|
||||
|
||||
export type WsMessage =
|
||||
| { type: 'execution_start'; data: { prompt_id: string } }
|
||||
| { type: 'executing'; data: { node: string; prompt_id: string } }
|
||||
| { type: 'execution_complete'; data: { prompt_id: string } }
|
||||
| { type: 'execution_error'; data: { node_id: string; message: string } }
|
||||
| { type: 'preview'; data: { node_id: string; image: PreviewImage } }
|
||||
| { type: 'table'; data: { node_id: string; rows: Array<Record<string, unknown>> } }
|
||||
| { type: 'scalar'; data: { node_id: string; value: number | string; unit?: string } }
|
||||
| { type: 'node_timing'; data: { node_id: string; elapsed_ms: number } }
|
||||
| { type: 'mesh3d'; data: { node_id: string; mesh: unknown } }
|
||||
| { type: 'overlay'; data: { node_id: string; overlay: OverlayData } }
|
||||
| { type: 'node_warning'; data: { node_id: string; message: string } }
|
||||
| { type: 'nodes_updated'; data: Record<string, never> };
|
||||
|
||||
// ── Widget description (used by nodeWidgetLayout) ────────────────────
|
||||
|
||||
export interface WidgetDescriptor {
|
||||
name: string;
|
||||
type: string | string[];
|
||||
opts: InputOptions;
|
||||
socketType?: string;
|
||||
}
|
||||
|
||||
// ── Node Context (provided by App to CustomNode) ─────────────────────
|
||||
|
||||
export interface NodeContextValue {
|
||||
executingNodeId: string | null;
|
||||
onWidgetChange: (nodeId: string, name: string, value: unknown) => void;
|
||||
openFileBrowser: (callback: (files: File[]) => void, options?: unknown) => void;
|
||||
openHelp: (label: string) => void;
|
||||
getTableColumns: (nodeId: string, inputName: string) => string[];
|
||||
getMeasurementChoices: (nodeId: string, inputName: string) => string[];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { toBlob } from 'html-to-image';
|
||||
import { CANVAS_COLORS } from './constants.js';
|
||||
import { CANVAS_COLORS } from './constants.ts';
|
||||
import { CAPTURE_SELECTOR as linePlotSelector } from './LinePlotOverlay';
|
||||
import { CAPTURE_SELECTOR as thresholdSelector } from './ThresholdHistogram';
|
||||
import { CAPTURE_SELECTOR as csSelector } from './CrossSectionOverlay';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.js';
|
||||
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.js';
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.ts';
|
||||
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.ts';
|
||||
|
||||
function mergeDefinition(nodeData, defs) {
|
||||
const savedData = nodeData || {};
|
||||
@@ -5,7 +5,7 @@
|
||||
* portable across machines and sessions.
|
||||
*/
|
||||
|
||||
import * as api from './api';
|
||||
import * as api from './api.ts';
|
||||
|
||||
const MAX_PACKED_BYTES = 100 * 1024 * 1024; // 100 MB
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.js';
|
||||
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.ts';
|
||||
|
||||
export function serializeWorkflowState(nodes, edges) {
|
||||
const compactObject = (value) => {
|
||||
Reference in New Issue
Block a user