Editor: interactive composition wireframe — drag to reorder and restructure

Drag a view's box (or Alt+up/down) to reorder it within its container; drag onto another view's edge to pair the two in a new row/column, move across containers, or insert. Each leaf shows a glyph of its mark type.

Core: spec-restructure.wrapViews (wrap, with flatten-to-insert, collapse, and data-pin invariants) and spec-insert.moveViewTo; the editor applies every drag as one undoable edit via AppStore.composeRequest, keeping the editor the single text source.
This commit is contained in:
2026-06-29 14:25:31 +03:00
parent 57604a80a6
commit 216797ff9b
16 changed files with 1160 additions and 56 deletions
+83
View File
@@ -35,6 +35,19 @@ export type IconName =
| 'info' // about / information — Carbon Information (outline)
| 'revert' // revert draft to last published — Carbon Reset
| 'structure' // composition-structure wireframe disclosure (preview toolbar) — nested view blocks
// Mark sub-family (composition wireframe leaves) — a simplified glyph of a unit
// view's mark type, so which-is-which reads at a glance. Vega-Lite mark synonyms
// collapse onto these via `markIconName` (CompositionWireframe); unknown → generic.
| 'mark-bar'
| 'mark-line'
| 'mark-area'
| 'mark-point'
| 'mark-arc'
| 'mark-rect'
| 'mark-tick'
| 'mark-rule'
| 'mark-text'
| 'mark-generic'
// Pane-toggle sub-family (spec §01A): a panel frame with one region filled, so the
// glyph shows *which* pane it controls by position (left / centre / right).
| 'pane-library' // toggle the library pane (left)
@@ -164,6 +177,76 @@ const GLYPHS: Record<IconName, ReactNode> = {
<rect x="9" y="17" width="14" height="5" />
</>
),
// Mark glyphs: simplified renderings of each Vega-Lite mark, drawn on the same
// 32-grid. Stroke-based where a line reads truer than a fill (line/rule/generic).
'mark-bar': (
<>
<rect x="5" y="14" width="5" height="14" />
<rect x="13" y="8" width="5" height="20" />
<rect x="21" y="18" width="5" height="10" />
</>
),
'mark-line': (
<polyline
points="4,22 11,13 18,17 28,6"
fill="none"
stroke="currentColor"
strokeWidth={2.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
),
'mark-area': <path d="M4,28 L4,17 L12,11 L20,16 L28,7 L28,28 Z" />,
'mark-point': (
<>
<circle cx="9" cy="20" r="2.6" />
<circle cx="16" cy="12" r="2.6" />
<circle cx="22" cy="22" r="2.6" />
<circle cx="25" cy="9" r="2.6" />
</>
),
// A three-quarter pie wedge (one quadrant empty) reads as arc/pie at a glance.
'mark-arc': <path d="M16,16 L16,4 A12,12 0 1,1 4,16 Z" />,
'mark-rect': (
<>
<rect x="5" y="9" width="6" height="6" />
<rect x="13" y="9" width="6" height="6" />
<rect x="21" y="9" width="6" height="6" />
<rect x="5" y="17" width="6" height="6" />
<rect x="13" y="17" width="6" height="6" />
<rect x="21" y="17" width="6" height="6" />
</>
),
'mark-tick': (
<>
<rect x="6" y="10" width="2" height="12" />
<rect x="12" y="10" width="2" height="12" />
<rect x="18" y="10" width="2" height="12" />
<rect x="24" y="10" width="2" height="12" />
</>
),
'mark-rule': (
<line
x1="5"
y1="24"
x2="27"
y2="8"
fill="none"
stroke="currentColor"
strokeWidth={2.5}
strokeLinecap="round"
/>
),
'mark-text': (
<>
<rect x="6" y="8" width="20" height="3" />
<rect x="6" y="15" width="14" height="3" />
<rect x="6" y="22" width="18" height="3" />
</>
),
'mark-generic': (
<rect x="5" y="6" width="22" height="20" fill="none" stroke="currentColor" strokeWidth={2} />
),
moon: (
<path d="M13.5025,5.4136A15.0755,15.0755,0,0,0,25.096,23.6082a11.1134,11.1134,0,0,1-7.9749,3.3893c-.1385,0-.2782.0051-.4178,0A11.0944,11.0944,0,0,1,13.5025,5.4136M14.98,3a1.0024,1.0024,0,0,0-.1746.0156A13.0959,13.0959,0,0,0,16.63,28.9973c.1641.006.3282,0,.4909,0a13.0724,13.0724,0,0,0,10.702-5.5556,1.0094,1.0094,0,0,0-.7833-1.5644A13.08,13.08,0,0,1,15.8892,4.38,1.0149,1.0149,0,0,0,14.98,3Z" />
),