mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-11 08:07:15 +00:00
fix selection issues
This commit is contained in:
@@ -281,7 +281,7 @@
|
||||
style="width: 275px; height: 34px;"
|
||||
onmouseenter={() => (hover = true)}
|
||||
onmouseleave={() => (hover = false)}
|
||||
onpointerdown={stopPropagation(preventDefault(() => dispatch('pointerdown')))}
|
||||
onpointerdown={stopPropagation(preventDefault((e) => dispatch('pointerdown', e)))}
|
||||
>
|
||||
{#if deletable}
|
||||
<ModuleAcceptReject action={moduleAction ?? action} {id} />
|
||||
|
||||
@@ -395,11 +395,8 @@
|
||||
insert: (detail) => {
|
||||
onInsert?.(detail)
|
||||
},
|
||||
select: (mod: string | FlowModule) => {
|
||||
select: (modId) => {
|
||||
if (!notSelectable) {
|
||||
// TODO: Handle Ctrl/Cmd and Shift modifiers when node-level click events are available
|
||||
// For now, normal click behavior
|
||||
const modId = typeof mod === 'string' ? mod : mod.id
|
||||
selectionManager.selectId(modId)
|
||||
onSelect?.(modId)
|
||||
}
|
||||
@@ -478,10 +475,24 @@
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear SvelteFlow's internal selection by creating new nodes array
|
||||
function clearFlowSelection() {
|
||||
nodes = nodes.map((node) => {
|
||||
if (node.selected) {
|
||||
return { ...node, selected: false }
|
||||
}
|
||||
return node
|
||||
})
|
||||
}
|
||||
|
||||
// Keyboard event handling
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
selectionManager.handleKeyDown(event, nodes)
|
||||
noteManager.handleKeyDown(event)
|
||||
if (event.key === 'Escape') {
|
||||
// Clear SvelteFlow's internal selection state
|
||||
clearFlowSelection()
|
||||
}
|
||||
if (noteMode) {
|
||||
exitNoteMode?.()
|
||||
}
|
||||
@@ -786,12 +797,13 @@
|
||||
selectionOnDrag={selectionManager.mode === 'rect-select'}
|
||||
elementsSelectable={true}
|
||||
selectionMode={SelectionMode.Partial}
|
||||
selectionKey={selectionManager.mode === 'rect-select' ? null : 'Meta'}
|
||||
selectionKey={selectionManager.mode === 'rect-select' || !editMode ? null : 'Meta'}
|
||||
panActivationKey={selectionManager.mode === 'rect-select' ? 'Meta' : null}
|
||||
panOnDrag={selectionManager.mode === 'rect-select' ? [1] : true}
|
||||
zoomOnDoubleClick={false}
|
||||
elevateNodesOnSelect={false}
|
||||
{proOptions}
|
||||
multiSelectionKey={'Meta'}
|
||||
nodesDraggable={false}
|
||||
--background-color={false}
|
||||
>
|
||||
@@ -815,7 +827,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- SelectionTool for handling selection changes and filtering -->
|
||||
<SelectionTool {nodes} {modules} {selectionManager} />
|
||||
<SelectionTool {selectionManager} />
|
||||
|
||||
{#if leftHeader}
|
||||
<div class="absolute top-2 left-2 z-10">
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { useOnSelectionChange, useStore, type Node } from '@xyflow/svelte'
|
||||
|
||||
import type { SelectionManager } from './selectionUtils.svelte'
|
||||
interface Props {
|
||||
nodes: any[]
|
||||
modules?: any[]
|
||||
selectionManager: any
|
||||
selectionManager: SelectionManager
|
||||
}
|
||||
|
||||
let { nodes, modules, selectionManager }: Props = $props()
|
||||
let { selectionManager }: Props = $props()
|
||||
|
||||
// Get store to access selectionRect
|
||||
const store = useStore()
|
||||
@@ -17,35 +15,17 @@
|
||||
// Notes are already non-selectable, so no filtering needed
|
||||
const selectedNodeIds = selectedNodes.map((node: Node) => node.id)
|
||||
|
||||
if (selectedNodeIds.length > 0) {
|
||||
selectionManager.selectNodes(selectedNodeIds, false, modules, nodes)
|
||||
} else if (selectedNodes.length === 0) {
|
||||
// Clear selection when SvelteFlow selection is cleared
|
||||
selectionManager.clearSelection()
|
||||
}
|
||||
})
|
||||
|
||||
// Compute selection box bounds
|
||||
let selectionBoxBounds = $derived(() => {
|
||||
const rect = store.selectionRect
|
||||
if (!rect) {
|
||||
return null
|
||||
}
|
||||
|
||||
// selectionRect is already in the correct coordinate system relative to the flow container
|
||||
// Just return it directly
|
||||
return {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
// Only select nodes if multiple nodes are selected
|
||||
// To avoid conflicting with the node-level click events
|
||||
if (selectedNodeIds.length > 1) {
|
||||
selectionManager.selectNodes(selectedNodeIds, false)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- Render custom selection box during drag selection -->
|
||||
{#if selectionBoxBounds()}
|
||||
{@const bounds = selectionBoxBounds()!}
|
||||
{#if store.selectionRect}
|
||||
{@const bounds = store.selectionRect!}
|
||||
<div
|
||||
class="absolute rounded cursor-pointer bg-surface-selected/30 border border-accent/30 pointer-events-none"
|
||||
style="
|
||||
|
||||
@@ -596,7 +596,7 @@ export function graphBuilder(
|
||||
}
|
||||
|
||||
const resultNode: NodeLayout = {
|
||||
id: 'result',
|
||||
id: 'Result',
|
||||
data: {
|
||||
eventHandlers: eventHandlers,
|
||||
success: success,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import type { Node } from '@xyflow/svelte'
|
||||
|
||||
export interface SelectionState {
|
||||
@@ -14,6 +13,9 @@ export class SelectionManager {
|
||||
constructor() {}
|
||||
|
||||
selectId(id: string) {
|
||||
if (this.selectedIds.length === 1 && this.selectedIds[0] === id) {
|
||||
return
|
||||
}
|
||||
this.selectedIds = [id]
|
||||
}
|
||||
|
||||
@@ -27,56 +29,10 @@ export class SelectionManager {
|
||||
|
||||
set mode(mode: 'normal' | 'rect-select') {
|
||||
this.#selectionMode = mode
|
||||
// Note: No automatic selection clearing when changing modes - preserve current selection
|
||||
}
|
||||
|
||||
// Get hierarchical children of a node
|
||||
getNodeChildrenIds(nodeId: string, modules: FlowModule[] | undefined, nodes: Node[]): string[] {
|
||||
const module = modules?.find((m) => m.id === nodeId)
|
||||
if (!module) return []
|
||||
|
||||
const childrenIds: string[] = []
|
||||
|
||||
// For hierarchical modules, find all children between start and end using proper graph traversal
|
||||
if (
|
||||
module.value.type === 'forloopflow' ||
|
||||
module.value.type === 'whileloopflow' ||
|
||||
module.value.type === 'branchall' ||
|
||||
module.value.type === 'branchone'
|
||||
) {
|
||||
const endNodeId = `${nodeId}-end`
|
||||
const endNode = nodes.find((n) => n.id === endNodeId)
|
||||
|
||||
if (endNode) {
|
||||
// Traverse from end node back to start using parentIds
|
||||
const visited = new Set<string>()
|
||||
const toVisit = [endNodeId]
|
||||
|
||||
while (toVisit.length > 0) {
|
||||
const currentId = toVisit.shift()!
|
||||
if (visited.has(currentId) || currentId === nodeId) continue
|
||||
|
||||
visited.add(currentId)
|
||||
const currentNode = nodes.find((n) => n.id === currentId)
|
||||
|
||||
if (currentNode && (currentNode as any).parentIds) {
|
||||
const parentIds = (currentNode as any).parentIds as string[]
|
||||
for (const parentId of parentIds) {
|
||||
if (parentId !== nodeId && !visited.has(parentId)) {
|
||||
childrenIds.push(parentId)
|
||||
toVisit.push(parentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return childrenIds
|
||||
}
|
||||
|
||||
// Select nodes with optional hierarchical selection
|
||||
selectNodes(nodeIds: string[], addToExisting = false, modules?: FlowModule[], nodes?: Node[]) {
|
||||
selectNodes(nodeIds: string[], addToExisting = false) {
|
||||
// Guard against empty nodeIds or uninitialized state
|
||||
if (!nodeIds || nodeIds.length === 0) {
|
||||
if (!addToExisting) {
|
||||
@@ -85,59 +41,13 @@ export class SelectionManager {
|
||||
return
|
||||
}
|
||||
|
||||
const newSelection = addToExisting ? [...this.selectedIds] : []
|
||||
const newSelection = addToExisting ? [...this.selectedIds, ...nodeIds] : nodeIds
|
||||
|
||||
nodeIds.forEach((nodeId) => {
|
||||
// Only add valid node IDs that exist in the current nodes
|
||||
if (!nodes || nodes.some((node) => node.id === nodeId)) {
|
||||
if (!newSelection.includes(nodeId)) {
|
||||
newSelection.push(nodeId)
|
||||
}
|
||||
// Auto-select children for hierarchical modules
|
||||
if (modules && nodes) {
|
||||
const children = this.getNodeChildrenIds(nodeId, modules, nodes)
|
||||
children.forEach((childId) => {
|
||||
if (nodes.some((node) => node.id === childId) && !newSelection.includes(childId)) {
|
||||
newSelection.push(childId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.selectedIds = newSelection
|
||||
}
|
||||
|
||||
// Toggle node selection
|
||||
toggleNodeSelection(nodeId: string, modules?: FlowModule[], nodes?: Node[]) {
|
||||
const newSelection = [...this.selectedIds]
|
||||
if (newSelection.includes(nodeId)) {
|
||||
// Remove node
|
||||
const index = newSelection.indexOf(nodeId)
|
||||
newSelection.splice(index, 1)
|
||||
// Also remove children
|
||||
if (modules && nodes) {
|
||||
const children = this.getNodeChildrenIds(nodeId, modules, nodes)
|
||||
children.forEach((childId) => {
|
||||
const childIndex = newSelection.indexOf(childId)
|
||||
if (childIndex > -1) {
|
||||
newSelection.splice(childIndex, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Add node
|
||||
newSelection.push(nodeId)
|
||||
// Auto-select children for hierarchical modules
|
||||
if (modules && nodes) {
|
||||
const children = this.getNodeChildrenIds(nodeId, modules, nodes)
|
||||
children.forEach((childId) => {
|
||||
if (!newSelection.includes(childId)) {
|
||||
newSelection.push(childId)
|
||||
}
|
||||
})
|
||||
}
|
||||
// If the new selection is the same as the current selection, do nothing
|
||||
if (JSON.stringify(newSelection) === JSON.stringify($state.snapshot(this.selectedIds))) {
|
||||
return
|
||||
}
|
||||
|
||||
this.selectedIds = newSelection
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user