Let variables, fields, ports and list items be dragged into order (#310)
Variables of a behavior, the fields of a struct, the ports of a start or end node and the items of a list could only be added and removed. Changing their order meant removing and adding them again. Now each of them is dragged by the mark in front of its row. - sortBlocks (ui/blockDrag.ts) makes the rows of one column sortable. A block is every row it covers, so a struct moves with its fields and a list item with its nested list. The block is dimmed while it is carried, an accent line shows where it lands, and a scrolled panel follows the pointer at its edge. Less than 4 px stays a click. - The arithmetic is pure and lives in ui/sortable.ts: blockGap, gapLineY, dropSlot and edgeScroll. dropIndex of the tile strip moved there too, so the reorder maths sits in one file. - MoveVariableCommand, MoveStructFieldCommand and MovePortCommand build on the existing MoveCommand. Undo moves back. - Lists need no command. InlineList replaces the list as a whole, as it does for every other edit of its items. - The grip is the kind mark, for a list item its number. Moving only happens among siblings: a field stays in its struct. - On a Struct asset the move runs through WithStructTypes. Typed variables, Pack/Expand ports and split outputs follow the new order. - Values and edges hang on names, so only the order changes. Nodes running a behavior read their ports from the start node, the new order reaches them without a copy. - Not sortable by design: the variables of a placed object, the fields of a typed struct (moved on the Struct asset) and the picked ports of Variables/Properties nodes. - Test hook __gm.inspectorMark(label) finds the mark of a row. Tests: - Gate on melektaus-atx: 235 files, 3572 tests, CHECK EXIT 0 (+9). - tests/core/reorder.test.ts: a variable, a field two levels deep, a Struct asset reaching a typed variable and Pack, and start ports reaching the calling node with edges and result kept. - e2e 22: ALL PASSED, 39 checks (+4, sum 1739). 22f-iii drags a variable past a struct of several rows, then a field inside it. - Counter-probes: dropSlot by top edge instead of middle and a field move that ignores its path fail 3 unit tests. With sortBlocks switched off, the three order checks of 22f-iii fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf6043a066
commit
2c19c89878
18 files changed
+671
-83
No files matched your search
@@ -23,8 +23,8 @@ import { isGraphAsset } from '../model/Asset';
|
||||
import type { PropertyValue } from '../model/Properties';
|
||||
import { kindLabel } from '../properties/kinds';
|
||||
import type { Project } from '../model/Project';
|
||||
import { type Removed, removeFirst, removeWhere, restoreAt } from '../util/list';
|
||||
import { type Command, CommandError, RenameCommand } from './Command';
|
||||
import { moveItem, type Removed, removeFirst, removeWhere, restoreAt } from '../util/list';
|
||||
import { type Command, CommandError, MoveCommand, RenameCommand } from './Command';
|
||||
import { nextFieldName } from './naming';
|
||||
import { requireGraph, requireNode, requireNodeDef } from './require';
|
||||
|
||||
@@ -303,6 +303,29 @@ export class RenamePortCommand extends RenameCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edges hang on a port's name, so they stay connected; a running or calling node takes the order
|
||||
* from here, as it takes the ports themselves (#310).
|
||||
*/
|
||||
export class MovePortCommand extends MoveCommand {
|
||||
constructor(
|
||||
private readonly assetId: string,
|
||||
private readonly nodeId: string,
|
||||
from: number,
|
||||
to: number,
|
||||
) {
|
||||
super('Reorder ports', from, to);
|
||||
}
|
||||
|
||||
protected move(project: Project, from: number, to: number): void {
|
||||
const node = requireNode(project, this.assetId, this.nodeId);
|
||||
const ports = [...(node.ports ?? [])];
|
||||
if (!moveItem(ports, from, to))
|
||||
throw new CommandError(`There is no port ${from + 1} to move to place ${to + 1}`);
|
||||
node.ports = ports;
|
||||
}
|
||||
}
|
||||
|
||||
/** Renames one declared port on one node, taking its edges and its typed-in value along. */
|
||||
function renamePortOn(graph: Graph, node: GraphNode, from: string, to: string): void {
|
||||
const ports = node.ports ?? [];
|
||||
|
||||
@@ -27,8 +27,8 @@ import {
|
||||
variableDef,
|
||||
} from '../properties/PropertySchema';
|
||||
import { valueAt, withValueAt } from '../properties/valuePath';
|
||||
import { type Removed, removeFirst, restoreAt } from '../util/list';
|
||||
import { type Command, CommandError, RenameCommand } from './Command';
|
||||
import { moveItem, type Removed, removeFirst, restoreAt } from '../util/list';
|
||||
import { type Command, CommandError, MoveCommand, RenameCommand } from './Command';
|
||||
import { renameVariablePorts } from './portCommands';
|
||||
import { requireBehavior, requireVariableOwner } from './require';
|
||||
|
||||
@@ -183,6 +183,22 @@ export class RenameVariableCommand extends RenameCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/** Only the order changes: values and the ports naming a variable go by its name (#310). */
|
||||
export class MoveVariableCommand extends MoveCommand {
|
||||
constructor(
|
||||
private readonly assetId: string,
|
||||
from: number,
|
||||
to: number,
|
||||
) {
|
||||
super('Reorder variables', from, to);
|
||||
}
|
||||
|
||||
protected move(project: Project, from: number, to: number): void {
|
||||
if (!moveItem(requireVariableOwner(project, this.assetId).variables, from, to))
|
||||
throw new CommandError(`There is no variable ${from + 1} to move to place ${to + 1}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** The behavior, once it is known to declare `key`. */
|
||||
function requireBehaviorVariable(project: Project, assetId: string, key: string): BehaviorAsset {
|
||||
const asset = requireBehavior(project, assetId);
|
||||
@@ -467,3 +483,25 @@ export class RenameStructFieldCommand extends RenameCommand {
|
||||
site.write(withFieldRenamed(site.read(), from, to));
|
||||
}
|
||||
}
|
||||
|
||||
/** A struct value is read by field name, so only the declaration changes order (#310). */
|
||||
export class MoveStructFieldCommand extends MoveCommand {
|
||||
/** `path` names the struct whose fields are reordered: none for the variable itself. */
|
||||
constructor(
|
||||
private readonly assetId: string,
|
||||
private readonly key: string,
|
||||
from: number,
|
||||
to: number,
|
||||
private readonly path: readonly string[] = [],
|
||||
) {
|
||||
super('Reorder fields', from, to);
|
||||
}
|
||||
|
||||
protected move(project: Project, from: number, to: number): void {
|
||||
const owner = requireFieldOwner(project, this.assetId, this.key, this.path);
|
||||
const fields = [...owner.fields()];
|
||||
if (!moveItem(fields, from, to))
|
||||
throw new CommandError(`There is no field ${from + 1} to move to place ${to + 1}`);
|
||||
owner.setFields(fields);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user