Files
windmill/frontend/src/lib/components/graph/groupDetectionUtils.ts
Guilhem cfeb294308 feat(frontend): add notes to flow (#6628)
* Add note component

* save note size and position

* move add note button up

* nit

* Add markdown support

* wip

* fix add sticky note button

* fix text update

* Add sticky note to saved flow data

* add note color picker

* Introduce node multiselect

* Add group notes

* Adapt layout to group node

* create a note manager class

* clean reactivity

* clean

* improve adaptive layout to group note

* modify layout based on cached text height

* fined grained graph rendering for notes

* separate noteManager into editor and render

* separate noteManager into editor and render

* create a note change observer

* render note node from context

* simplify note state managment

* show note in flow viewer

* clean dirty changes

* clean selection manager

* fix layout check

* improve bg surface select

* Handle z-index for stacked group notes

* clean selection manager

* exclude notes from rect select

* Allow switch between selection modes with keyboard keys

* improve selection box styling

* prevent dragging note when editing

* nit

* Simplify selection using svelte flow built in feature

* handle note selection separately

* Add min size for notes

* improve selection toggle

* improve mode switch

* make size and position optional for group notes

* Improve initial viewport position

* Add context menu for the canevas

* nit

* Add node context menu

* improve note select

* use clickoutside for note deselect

* use pointerdown outside to close context menu

* nit

* fix selection issues

* make edges non selectable

* improve color palette

* fix backend

* fix backend check

* cargo lock restore

* Add toggle to display notes

* fix note selection

* nit

* account for css offset in for loop

* fix multiple selection pannel styling

* clear flow selection when creating note

* Improve placeholder and note default text

* Escape note edit mode when pressing Esc

* Allow note edition in local dev

* clean

* Handle subflow selection

* prevent group note resizing

* nit

* allow notes in flow expand

* Improve multi select panel

* Allow context menu in note mode

* Add event listenner to fix pane click deselect

* prevent zoom in text area in notes

* improve bounding box styling

* Use control for box selection for non mac users

* nit

* clean notes groups

* nit

* use portal for note actions

* handle assets node when computing note layout

* Simplify layout compute for notes

* use smart color choice for notes

* Switch display note when adding a new note

* clean code

* improve group note bound size calculation

* simplify AI tool nodes and asset handling

* nit

* nit

* improve flow centering

* create group note button

* Improve selection of nodes

* Revert "Improve selection of nodes"

This reverts commit d2c40d82b1.

* refert backend changes

* nit

* improve graph selection

* clean

* make backend work except job runs

* fix notSelectable

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-11-19 21:25:57 +00:00

87 lines
2.5 KiB
TypeScript

type FlowNode = { id: string; parentIds?: string[] }
/**
* Use a simple algorithm to complete a group and split it into connected components
*/
export function completeAndSplitGroup(groupNodes: string[], flowNodes: FlowNode[]): string[][] {
if (groupNodes.length <= 1) {
return groupNodes.length === 1 ? [groupNodes] : []
}
// Build parent map for upward traversal only
const parents = new Map<string, string[]>()
for (const node of flowNodes) {
parents.set(node.id, node.parentIds || [])
}
const groupSet = new Set(groupNodes)
const assignedComponent = new Map<string, number>()
const components: Array<Set<string>> = []
const mergeComponents = (fromIdx: number, toIdx: number): void => {
if (fromIdx === toIdx) return
const target = components[toIdx]
const source = components[fromIdx]
source.forEach((node) => target.add(node))
source.clear()
for (const [nodeId, idx] of assignedComponent.entries()) {
if (idx === fromIdx) {
assignedComponent.set(nodeId, toIdx)
}
}
}
for (const startNode of groupNodes) {
if (assignedComponent.has(startNode)) continue
const componentIdx = components.length
components.push(new Set([startNode]))
assignedComponent.set(startNode, componentIdx)
const stack: { nodeId: string; path: string[]; seen: Set<string> }[] = [
{ nodeId: startNode, path: [startNode], seen: new Set([startNode]) }
]
while (stack.length > 0) {
const { nodeId, path, seen } = stack.pop()!
const parentIds = parents.get(nodeId) || []
for (const parentId of parentIds) {
if (seen.has(parentId)) continue
const newPath = [...path, parentId]
const newSeen = new Set(seen)
newSeen.add(parentId)
if (groupSet.has(parentId)) {
const existingIdx = assignedComponent.get(parentId)
if (existingIdx === undefined) {
assignedComponent.set(parentId, componentIdx)
components[componentIdx].add(parentId)
stack.push({ nodeId: parentId, path: [parentId], seen: new Set([parentId]) })
} else if (existingIdx !== componentIdx) {
mergeComponents(existingIdx, componentIdx)
}
for (const node of newPath) {
components[componentIdx].add(node)
}
} else {
stack.push({ nodeId: parentId, path: newPath, seen: newSeen })
}
}
}
}
return components
.filter((component) => component.size > 0)
.map((component) =>
Array.from(component)
.filter((nodeId) => !nodeId.startsWith('subflow:'))
.sort()
)
.filter((component) => component.length > 0)
}