Files
windmill/frontend/src/lib/components/graph/selectionUtils.svelte.ts
T
GuilhemandRuben Fiszel 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

107 lines
2.6 KiB
TypeScript

import type { Node } from '@xyflow/svelte'
export class SelectionManager {
#selectedNodes = $state<Node[] | { id: string }[]>([])
#selectionMode = $state<'normal' | 'rect-select'>('normal')
#clearGraphSelection: () => void = () => {}
constructor() {}
setClearGraphSelection(clearGraphSelection: () => void) {
this.#clearGraphSelection = clearGraphSelection
}
selectId(id: string) {
if (this.#selectedNodes.length === 1 && this.#selectedNodes[0].id === id) {
return
}
this.#clearGraphSelection()
this.#selectedNodes = [{ id }]
}
getSelectedId(): string {
if (this.#selectedNodes.length === 0) {
return 'settings'
}
const selectedNode = this.#selectedNodes[0]
if (selectedNode['type'] === 'branchOneEnd') {
const id = selectedNode.id.replace(/-end$/, '')
if (id !== '') {
return id
}
} else if (selectedNode['type'] === 'branchAllEnd') {
const id = selectedNode.id.replace(/-end$/, '')
if (id !== '') {
return id
}
} else if (selectedNode['type'] === 'forLoopStart') {
const id = selectedNode.id.replace(/-start$/, '')
if (id !== '') {
return id
}
} else if (selectedNode['type'] === 'forLoopEnd') {
const id = selectedNode.id.replace(/-end$/, '')
if (id !== '') {
return id
}
} else if (selectedNode['type'] === 'subflowBound') {
const id = selectedNode.id.replace(/-subflow-end$/, '')
if (id !== '') {
return id
}
}
return selectedNode.id
}
get mode() {
return this.#selectionMode
}
set mode(mode: 'normal' | 'rect-select') {
this.#selectionMode = mode
}
get selectedIds() {
if (this.#selectedNodes.length === 0) {
return ['settings']
}
return [...this.#selectedNodes.map((node) => node.id)]
}
// Select nodes with optional hierarchical selection
selectNodes(nodes: Node[]) {
// Guard against empty nodeIds or uninitialized state
if (!nodes || nodes.length === 0) {
this.clearSelection()
return
}
// If the new selection is the same as the current selection, do nothing
if (JSON.stringify(nodes) === JSON.stringify($state.snapshot(this.#selectedNodes))) {
return
}
this.#selectedNodes = nodes
}
// Clear all selections
clearSelection() {
this.#selectedNodes = [{ id: 'settings' }]
}
// Check if a node is selected
isNodeSelected(nodeId: string): boolean {
return this.#selectedNodes.some((node) => node.id === nodeId)
}
// Handle keyboard shortcuts
handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
// Escape key clears selection regardless of mode
this.clearSelection()
this.#clearGraphSelection()
}
}
}