Files
windmill/frontend/src/lib/components/graph/MoveHandleButton.svelte
T
GuilhemandClaude Opus 4.6 c0c9388415 feat: add move, delete, and duplicate to flow node context menu (#8050)
* feat: add context menu, multi-select actions, and keyboard shortcuts to flow editor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review feedback on context menu PR

- Revert accidental static import of @scalar/openapi-parser (keep lazy-loaded)
- Restore [data-context-menu] in portalDivs for clickOutside compatibility
- Make noteDisabled reactive ($derived) in ModuleNode
- Use platform-aware shortcut hint (⌫ on Mac, Del on Windows/Linux)
- Optimize resolveSelectedModuleIds with single-pass ancestor map

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address additional review feedback on flow context menu PR

- Use $derived.by instead of $derived for computed bounds in SelectionBoundingBox
- Remove redundant structuredClone wrappers around $state.snapshot
- Add null guard for originalModules/targetModules in move handler
- Add upper-bound guard (n < 10000) to copyId loop
- Fix fragile toggle comparison in moveManager with full array equality

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:53:01 +00:00

75 lines
1.7 KiB
Svelte

<script lang="ts">
import { stopPropagation, preventDefault } from 'svelte/legacy'
import { onDestroy } from 'svelte'
import { Move } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import type { MoveManager } from './moveManager.svelte'
interface Props {
moveManager: MoveManager
moduleId: string
selectedIds?: string[]
singleNode?: boolean
visible?: boolean
onClickMove?: () => void
class?: string
}
let {
moveManager,
moduleId,
selectedIds,
singleNode = false,
visible = true,
onClickMove,
class: extraClass
}: Props = $props()
let dragCleanup: (() => void) | undefined
function onPointerDown(e: Event) {
const pe = e as PointerEvent
const startX = pe.clientX
const startY = pe.clientY
let didDrag = false
function onMove(me: PointerEvent) {
const dx = me.clientX - startX
const dy = me.clientY - startY
if (!didDrag && Math.sqrt(dx * dx + dy * dy) > 5) {
didDrag = true
moveManager.startDrag(moduleId, startX, startY, singleNode ? undefined : selectedIds)
}
}
function onUp() {
document.removeEventListener('pointermove', onMove)
document.removeEventListener('pointerup', onUp)
dragCleanup = undefined
if (!didDrag) {
onClickMove?.()
}
}
document.addEventListener('pointermove', onMove)
document.addEventListener('pointerup', onUp)
dragCleanup = onUp
}
onDestroy(() => dragCleanup?.())
</script>
<button
class={twMerge(
'center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary cursor-grab',
visible ? '' : '!hidden',
'shadow-md rounded-md',
'touch-none',
extraClass
)}
onpointerdown={stopPropagation(preventDefault(onPointerDown))}
title="Move"
>
<Move size={12} />
</button>