Give unconnected node inputs the default the node shows (#234)
A new node handed null to every unconnected input that had no port default, even a Bool. The inspector showed false, 0, "" or an empty list at the same time, so what the node read was not what the editor said. 97 of 135 inputs of the built-in nodes were affected. - inlineValue() falls back to the default the inspector shows. One place covers every node, also future ones and saved projects. - A default list is handed out as a fresh copy per read, so a node that edits its input in place cannot change the shared default. - The node shows the value it reads: an empty text or list as (empty), an object or struct input without a link as (null). Live values while playing use the same words. - A key input without a key keeps showing a dash, like the key field in the inspector. - Visible changes in old projects: To text without input gives "" instead of "0", Select and First not empty give "" instead of null. Tests: - 8 new unit tests in tests/core/inline-defaults.test.ts, among them one over all built-in nodes that no typed-in input reads null. - The e2e check in module 6 reads the rows of an unlinked Name of node. The check sum stays the same. - ./check.sh: 167 files, 2434 tests, exit 0. e2e 6: 117 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b44f97c35
commit
1d18862ed6
4 files changed
+149
-10
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import type { PropertyBag, PropertyValue } from '../model/Properties';
|
||||
import { isList, type PropertyBag, type PropertyValue } from '../model/Properties';
|
||||
import { isKindWith, plainKindOf } from '../properties/kinds';
|
||||
import { flatDef, type PropertyDef } from '../properties/PropertySchema';
|
||||
import type { DeclaredPort, Graph, GraphNode, PortType } from './Graph';
|
||||
@@ -11,7 +11,7 @@ export interface PortDef {
|
||||
key: string;
|
||||
label: string;
|
||||
type: PortType;
|
||||
/** Inline value used while the input is unconnected and the node has no own value (default 0/''/false). */
|
||||
/** Inline value used while the input is unconnected and the node has no own value (else 0/''/false). */
|
||||
default?: PropertyValue;
|
||||
/** The property this port carries; its typed-in value is edited as that property (color, enum, …). */
|
||||
field?: PropertyDef;
|
||||
@@ -200,7 +200,24 @@ export function inlineInputDef(port: PortDef): PropertyDef {
|
||||
return def.kind === 'number' ? { ...def, step: 1 } : def;
|
||||
}
|
||||
|
||||
/** The value an unconnected input carries: the node's own inline value, else the port default. */
|
||||
/**
|
||||
* The value an unconnected input carries: the node's own inline value, else the default the
|
||||
* inspector shows for it – `false`, `0`, `""`, an empty list –, so what the node reads is what the
|
||||
* editor says it reads. Only an input with nothing to type in (object, struct, exec) is `null`.
|
||||
*/
|
||||
export function inlineValue(node: { params: PropertyBag }, port: PortDef): Value {
|
||||
return node.params[port.key] ?? port.default ?? null;
|
||||
return node.params[port.key] ?? port.default ?? shownDefault(port);
|
||||
}
|
||||
|
||||
/** Cached per port: the interpreter asks for it on every read of an unconnected input. */
|
||||
const shownDefaults = new WeakMap<PortDef, PropertyValue | null>();
|
||||
|
||||
function shownDefault(port: PortDef): Value {
|
||||
let value = shownDefaults.get(port);
|
||||
if (value === undefined) {
|
||||
value = isInlinePort(port) ? inlineInputDef(port).default : null;
|
||||
shownDefaults.set(port, value);
|
||||
}
|
||||
// A list is handed out fresh: a node that edits its input in place must not edit every other one.
|
||||
return isList(value) ? [...value] : value;
|
||||
}
|
||||
@@ -184,10 +184,20 @@ export class NodeView extends Widget {
|
||||
.map((p) => p.key),
|
||||
);
|
||||
this.inlineTexts = new Map();
|
||||
// Every free input shows what it carries, its default too: an untouched `And` reads "false",
|
||||
// not nothing, empty text reads "(empty)" – the same value the node computes with. A free
|
||||
// object or struct has nothing to type in and carries null, which it says as well.
|
||||
for (const port of inlineInputs(this.def)) {
|
||||
const value = inlineValue(node, port);
|
||||
if (!this.connected.has(port.key) && value !== null && value !== '')
|
||||
this.inlineTexts.set(port.key, formatField(inlineInputDef(port), value, project));
|
||||
if (this.connected.has(port.key)) continue;
|
||||
const text = formatField(inlineInputDef(port), inlineValue(node, port), project);
|
||||
this.inlineTexts.set(port.key, text === '' ? EMPTY_TEXT : text);
|
||||
}
|
||||
for (const port of this.def.inputs) {
|
||||
if (
|
||||
(port.type === 'object' || port.type === 'struct') &&
|
||||
edgesInto(graph, node.id, port.key).length === 0
|
||||
)
|
||||
this.inlineTexts.set(port.key, NULL_TEXT);
|
||||
}
|
||||
this.linked = {
|
||||
inputs: graph.edges.some((e) => e.to.node === node.id),
|
||||
@@ -489,9 +499,13 @@ function formatField(def: PropertyDef, value: Value, project: Project | null): s
|
||||
return referenceLabel(def, project, asString(value)) ?? formatValue(value);
|
||||
}
|
||||
|
||||
/** How a node says "no value" and "empty value" – for inputs and live outputs alike. */
|
||||
const NULL_TEXT = '(null)';
|
||||
const EMPTY_TEXT = '(empty)';
|
||||
|
||||
function formatLive(output: PortDef, value: Value, project: Project | null): string {
|
||||
if (value === null) return '—';
|
||||
if (value === '') return '""';
|
||||
if (value === null) return NULL_TEXT;
|
||||
if (value === '') return EMPTY_TEXT;
|
||||
return output.field ? formatField(output.field, value, project) : formatValue(value);
|
||||
}
|
||||
|
||||
@@ -500,7 +514,7 @@ function formatValue(value: Value): string {
|
||||
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
||||
// A list can hold hundreds of items – shown whole it would stretch the node across the graph.
|
||||
if (isList(value)) {
|
||||
if (value.length === 0) return 'empty';
|
||||
if (value.length === 0) return EMPTY_TEXT;
|
||||
const head = value.slice(0, 4).map(formatValue).join(', ');
|
||||
return value.length > 4 ? `${head}, … (${value.length})` : head;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user