`; here the lane
diff --git a/frontend/src/lib/components/graph/simplified/SimplifiedNode.svelte b/frontend/src/lib/components/graph/simplified/SimplifiedNode.svelte
index 5e97e899e2..f00d067032 100644
--- a/frontend/src/lib/components/graph/simplified/SimplifiedNode.svelte
+++ b/frontend/src/lib/components/graph/simplified/SimplifiedNode.svelte
@@ -10,6 +10,9 @@
import { SIMPLIFIED, type SimplifiedNodeData } from './simplifiedLayout'
import { getSimplifiedEditContext, useStepMenu } from './simplifiedContext'
import SimplifiedSlot from './SimplifiedSlot.svelte'
+ import GroupHeaderBlock from '../GroupHeaderBlock.svelte'
+ import GroupModuleIcons from '../GroupModuleIcons.svelte'
+ import { NOTE_COLORS, NoteColor } from '../noteColors'
let { id, data, selected }: { id: string; data: SimplifiedNodeData; selected?: boolean } =
$props()
@@ -87,6 +90,30 @@
{/if}
+{:else if data.kind === 'collapsedGroup' && data.group}
+ {@const groupColors =
+ NOTE_COLORS[(data.group.value.color as NoteColor) ?? NoteColor.BLUE] ??
+ NOTE_COLORS[NoteColor.BLUE]}
+
+
+ {#if data.group.modules.length}
+
+
+
+ {/if}
+
{:else if data.kind === 'junction'}
text.length * 7
@@ -107,4 +112,39 @@ describe('layoutSimplifiedFlow', () => {
expect(junction.position.x).toBeGreaterThan(byId.a.position.x + byId.a.width!)
expect(junction.position.x + junction.width!).toBeLessThan(byId.c.position.x)
})
+
+ it('gives every "+" the insert payload the editor resolves, branch-one default being branch 0', () => {
+ const modules: FlowModule[] = [
+ step('a', 'A'),
+ {
+ id: 'b',
+ value: {
+ type: 'branchone',
+ branches: [{ expr: 'x', modules: [step('c', 'C')] }],
+ default: []
+ }
+ }
+ ]
+ const { nodes } = layoutSimplifiedFlow({ modules, editable: true }, measure)
+ const inserts = nodes.flatMap((n) => {
+ const { insert } = n.data as SimplifiedNodeData
+ return insert ? [{ id: n.id, ...insert }] : []
+ })
+ const at = (index: number, rootId?: string, branch?: number) =>
+ inserts.find(
+ (i) => i.index === index && i.branch?.rootId === rootId && i.branch?.branch === branch
+ )
+
+ // Top level: between Input and a (the only place a trigger may go), and before Result
+ expect(at(0)).toMatchObject({ sourceId: 'Input', targetId: 'a', allowTrigger: true })
+ expect(at(2)).toMatchObject({ sourceId: 'b', targetId: 'Result', allowTrigger: false })
+ // Inside the condition's lane, which the step tree numbers after the default
+ expect(at(0, 'b', 1)).toMatchObject({ targetId: 'c', ancestors: ['b'] })
+ expect(at(1, 'b', 1)).toMatchObject({ sourceId: 'c' })
+ // The empty default lane's dot is its own "+"
+ expect(inserts.find((i) => i.id === 'b:empty-0')).toMatchObject({
+ index: 0,
+ branch: { rootId: 'b', branch: 0 }
+ })
+ })
})
diff --git a/frontend/src/lib/components/graph/simplified/simplifiedLayout.ts b/frontend/src/lib/components/graph/simplified/simplifiedLayout.ts
index bf5efdb719..a53ab9e618 100644
--- a/frontend/src/lib/components/graph/simplified/simplifiedLayout.ts
+++ b/frontend/src/lib/components/graph/simplified/simplifiedLayout.ts
@@ -3,7 +3,7 @@ import type { FlowModule } from '$lib/gen'
import { prettyLanguage } from '$lib/common'
import { getAllModules } from '$lib/components/flows/flowExplorer'
import { buildStructureTree, type FlowStructureNode } from '../flowStructure'
-import type { FlowGroup } from '../groupEditor.svelte'
+import { GROUP_HEADER_HEIGHT, type FlowGroup } from '../groupEditor.svelte'
/**
* Left-to-right layout for the simplified flow view. Each layer is a column whose width is its
@@ -28,6 +28,7 @@ export const SIMPLIFIED = {
trunkGap: 88,
/** Column gap holding a junction, with a trunk on each side of it */
junctionGap: 120,
+ collapsedGroupWidth: 240,
/** Extra box side padding and column gap in the editor, where "+" buttons sit there */
editRing: 12,
editGap: 32
@@ -46,6 +47,8 @@ export type SimplifiedNodeKind =
| 'caption'
/** A "+" between two steps, only laid out when the graph is editable */
| 'slot'
+ /** A collapsed group, drawn as one card in place of its steps */
+ | 'collapsedGroup'
export type SimplifiedNodeData = {
kind: SimplifiedNodeKind
@@ -61,6 +64,8 @@ export type SimplifiedNodeData = {
insert?: InsertTarget
/** On a branch caption, when that branch can be deleted */
deleteBranch?: { id: string; index: number }
+ /** On a collapsed group: the group, keyed by `groupKey`, and the steps it hides */
+ group?: { key: string; value: FlowGroup; modules: FlowModule[] }
}
/**
@@ -86,8 +91,10 @@ export type SimplifiedBoxData = {
width: number
height: number
header: SimplifiedNodeData
- /** Only for groups */
- group?: FlowGroup
+ /** Height reserved above the contents for the header (and a group's note) */
+ headerHeight: number
+ /** Only for groups, keyed by `groupKey` */
+ group?: { key: string; value: FlowGroup }
}
export type SimplifiedEdgeData = {
@@ -272,7 +279,8 @@ type Box = {
id: string
kind: SimplifiedBoxKind
header: SimplifiedNodeData
- group?: FlowGroup
+ headerHeight: number
+ group?: { key: string; value: FlowGroup }
startLayer: number
endLayer: number
y: number
@@ -307,7 +315,13 @@ type Block = {
endLayer: number
}
-type Ctx = { measure: TextMeasurer; modules: Map; editable: boolean }
+type Ctx = {
+ measure: TextMeasurer
+ modules: Map
+ editable: boolean
+ collapsedGroups: Set
+ groupNoteHeights: Record
+}
/** Where a sequence of steps lives in the step tree, as the insert payload names it */
type Container = { rootRef?: string; branch: number; ancestors: string[] }
@@ -524,10 +538,11 @@ function layoutLane(
function boxed(
box: Pick,
layer: number,
- inner: Block
+ inner: Block,
+ headerHeight: number = SIMPLIFIED.loopHeader
): Block {
const pad = SIMPLIFIED.loopPad
- const top = pad + SIMPLIFIED.loopHeader
+ const top = pad + headerHeight
const height = top + inner.height + pad
const b = shift(inner, top)
return {
@@ -536,6 +551,7 @@ function boxed(
boxes: [
{
...box,
+ headerHeight,
startLayer: layer,
endLayer: inner.endLayer,
y: 0,
@@ -583,9 +599,23 @@ function groupHeader(group: FlowGroup | undefined, stepCount: number): Simplifie
}
function layoutNode(node: FlowStructureNode, layer: number, ancestors: string[], ctx: Ctx): Block {
- if (node.kind === 'group') {
+ if (node.kind === 'group' && node.group) {
+ const group = { key: node.id, value: node.group }
+ const noteHeight = ctx.groupNoteHeights[node.id] ?? 0
+ if (ctx.collapsedGroups.has(node.id)) {
+ const modules = (node.moduleIds ?? []).flatMap((id) => ctx.modules.get(id) ?? [])
+ const data: SimplifiedNodeData = {
+ kind: 'collapsedGroup',
+ title: node.group.summary || 'Group',
+ group: { ...group, modules },
+ width: SIMPLIFIED.collapsedGroupWidth,
+ // Header, then a row of the hidden steps' icons, as the full graph's collapsed card
+ height: GROUP_HEADER_HEIGHT + 34 + 8 + noteHeight
+ }
+ return leaf(`collapsed-group:${node.id}`, data, layer)
+ }
const rootRef = ref(node)
- const container = { rootRef, branch: 0, ancestors }
+ const container = { rootRef, branch: 0, ancestors: [...ancestors, rootRef] }
const lane = layoutLane(
node.branches[0]?.children ?? [],
layer,
@@ -595,7 +625,8 @@ function layoutNode(node: FlowStructureNode, layer: number, ancestors: string[],
ctx
)
const header = groupHeader(node.group, node.moduleIds?.length ?? 0)
- return boxed({ id: rootRef, kind: 'group', header, group: node.group }, layer, lane)
+ const headerHeight = GROUP_HEADER_HEIGHT + 6 + noteHeight
+ return boxed({ id: rootRef, kind: 'group', header, group }, layer, lane, headerHeight)
}
const mod = ctx.modules.get(node.id)
@@ -684,13 +715,19 @@ export function layoutSimplifiedFlow(
preprocessorModule?: FlowModule
/** Lays out the "+" slots, and a lane for empty loop bodies to insert into */
editable?: boolean
+ /** Keys of the groups currently collapsed, each drawn as one card */
+ collapsedGroups?: Set
+ /** Rendered height of each group's note, reserved under its header */
+ groupNoteHeights?: Record
},
measure: TextMeasurer = measureTextWidth
): SimplifiedLayout {
const ctx: Ctx = {
measure,
modules: new Map(getAllModules(opts.modules ?? []).map((m) => [m.id, m])),
- editable: opts.editable ?? false
+ editable: opts.editable ?? false,
+ collapsedGroups: opts.collapsedGroups ?? new Set(),
+ groupNoteHeights: opts.groupNoteHeights ?? {}
}
const structure = opts.structure ?? buildStructureTree(opts.modules ?? [], [])
@@ -828,6 +865,7 @@ export function layoutSimplifiedFlow(
boxBounds.set(b.id, { left, right })
const data: SimplifiedBoxData = {
kind: b.kind,
+ headerHeight: b.headerHeight,
width: right - left,
height: b.height,
header: b.header,