mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 08:03:50 +00:00
* feat: add flow group nodes core infrastructure Add group data model (start_id/end_id boundary pairs), GroupEditor for CRUD operations, groupDetectionUtils for membership computation and validation, GroupedModulesProxy for reactive sync, and compound layout support. Update openflow.openapi.yaml with group schema. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add group UI components and rendering Add GroupOverlay with bounding box and z-ordering, GroupHeader with StepCountTab and ellipsis menu, GroupNodeCard, GroupNoteArea for inline markdown notes, CollapsedGroupNode/CollapsedSubflowNode for collapsed rendering, GroupEndNode/GroupHeadNode boundary markers, and group actions in NodeContextMenu and SelectionBoundingBox. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: integrate groups into flow graph, builder, and existing components Wire group support into FlowGraphV2 (overlays, collapsed rendering, group-aware layout), graphBuilder (GroupedModule tree, container collapse/expand, group boundary nodes), BaseEdge (drop targets for group operations), ModuleNode (collapsed container rendering), and flow map components (schema item grouping). Remove SubflowBound in favor of CollapsedSubflowNode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove banned $bindable(default) pattern and dead ternary Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: decouple collapse state from grouped module tree Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: pass groups prop to FlowGraphV2 and use GroupDisplayState via graphContext Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove group membership system, compute nesting depth from visual bounds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: simplify GroupOverlay bounds, remove unused headerY and showNotes prop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: populate innerNodeIds for expanded subflow overlay Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove expanded subflow overlay feature for separate PR Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: flatten groups in getContainerModules to prevent crash on collapsed containers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add drag-to-move support for group nodes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: derive group boundaries from expanded membership to prevent splitting existing groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: catch group validation errors and display as flow graph alert Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add unit tests for group validation in buildGroupedModules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject virtual nodes (Input, Result, Trigger) from groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add virtual node rejection tests for buildGroupedModules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: exclude preprocessor and failure module from groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: disable Create group button when preprocessor is selected Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject selection entirely when it contains excluded nodes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove unnecessary excludeIds from buildGroupedModules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove debug console.log from FlowGraphV2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use cross-browser CSS grid trick for group summary input auto-sizing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: hide group boundary edges and reformat GroupNoteArea Hide edges between group header and first node, and between last node and group-end, keeping them in the DOM but visually hidden. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: stop FlowGraphV2 from reading groups via groupEditorContext Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: show module previews with status, selection, and suspend popover in collapsed groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: extract collapsible implicit containers to separate branch Remove collapse/expand functionality for implicit containers (forloops, while loops, branches) from this branch. Backed up as collapsible-implicit-containers-backup for later rebase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: use original reactive modules for graph node data instead of proxy snapshots Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: prevent node loss when moving into forloop inside a group Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: replace GroupedModule proxy with structure-only FlowStructureNode tree Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use "group-" prefix for group IDs instead of "note-" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update group boundaries when renaming a module ID When a module at a group boundary (start_id or end_id) is renamed, the group definitions now get updated before the reactive rebuild, preventing stale references that would break the flow structure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update graph layout when removing a group note Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add opaque background behind test run button to prevent see-through Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: detect and reject duplicate group IDs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: simplify group creation validation with early marker normalization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use $state.raw in MiniFlowGraph to avoid xyflow performance warning Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: address code review feedback - Revert backend traverse_modules change (not part of this feature) - Use Map for node lookup in GroupOverlay (O(1) vs O(n) per group) - Extract computeNodeExtraSpace to nodeExtraSpace.ts for testability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: address PR review feedback - Compute group depths from structure tree O(n) instead of O(n²) bounds comparison - Remove unnecessary $derived(groups) in GroupOverlay - Remove unused collapsed field from container types in OpenAPI spec - Use NODE.width constant in GroupNodeCard instead of hardcoded 275px - Add comment explaining intentional stale preservation in rebuild() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve flow groups during dependency job re-serialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve Svelte state_referenced_locally warnings in GroupHeader and FlowGraphV2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: show subflow groups when expanding a subflow in the graph - Store both modules and groups when expanding a subflow - Pass groups to buildStructureTree so group nodes render - Include subflow groups in overlay rendering and collapse tracking - Clone modules for prefix rewriting to avoid state_unsafe_mutation - Register expanded subflow modules in moduleMap before prefix rewriting - Disable group editing in expanded subflows and read-only views Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore accidentally removed code from main - Restore subflowBound selection handling in selectionUtils - Restore comments in SelectionBoundingBox - Restore deletable={false} in FirstStepInputs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove redundant adjacency check from MoveManager The disableMoveIds check already prevents all invalid drop targets, making the adjacencySourceId/adjacencyTargetId fields unnecessary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate auto-generated files after OpenAPI schema change Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate cli skills after main merge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: include groups in view_graph localStorage state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: centralize canCreateGroup and replace group note with group creation - Add canCreateGroup StateStore to GroupEditorContext, computed in FlowGraphV2 - Replace "Create group note" with "Create group" in FlowSelectionPanel - Remove "Add note" from selection bounding box dropdown - Remove unused NodeContextMenu component - Wire createGroup through FlowModuleSchemaMap → FlowGraphV2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject groups spanning parallel branches and surface ill-formed group errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate auto-generated files after main merge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: ensure modules appears before groups in YAML export Svelte 5's $state proxy registers groups as a tracked property before it's explicitly set, causing it to appear before modules in Object.keys iteration. Reorder the value object at export time for readable YAML. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: address second round of PR review feedback - Add comment explaining duplicateMultiple bypasses structure tree - Add warning log for inverted ranges in computeGroupModuleIds - Use NODE.width constant in CollapsedGroupNode instead of hardcoded 275px - Simplify redundant condition in getGroupsEmptiedBy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove stored group ID, derive ephemeral key from start_id:end_id Groups no longer store an `id` field. Instead, a `groupKey(g)` helper derives an ephemeral key from `${start_id}:${end_id}` at read time. This simplifies the schema while preserving all runtime functionality. When boundaries shift (module deletion), runtime state (collapse, note heights) is remapped to the new key via GroupDisplayState.remapGroupKey. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add note button, save/cancel hints, and rename collapsed_by_default to autocollapse Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: propagate selection from collapsed group badges to external listeners Pass eventHandlers to GroupModuleIcons so clicking a module badge calls both selectionManager.selectId (visual highlight) and eventHandlers.select (side panel propagation via onSelect). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate auto-generated files after main merge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: hide In/Out popovers and actions during click-to-move Replace isDragging with isMoving derived that covers both drag-move and click-move states, disabling popovers, delete button, and test run button during any move operation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
166 lines
4.5 KiB
Svelte
166 lines
4.5 KiB
Svelte
<script lang="ts">
|
|
import { Group } from 'lucide-svelte'
|
|
import { getNodeColorClasses } from '$lib/components/graph'
|
|
import { NOTE_COLORS, NoteColor } from './noteColors'
|
|
import { NODE } from './util'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import { preventDefault, stopPropagation } from 'svelte/legacy'
|
|
import GroupNoteArea from './GroupNoteArea.svelte'
|
|
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
|
import GroupModuleIcons from './GroupModuleIcons.svelte'
|
|
import type { FlowModule } from '$lib/gen'
|
|
|
|
interface Props {
|
|
summary?: string
|
|
selected?: boolean
|
|
stepCount?: number
|
|
color?: string
|
|
note?: string
|
|
showNote?: boolean
|
|
editMode?: boolean
|
|
modules?: FlowModule[]
|
|
onExpand?: () => void
|
|
onSummaryUpdate?: (text: string) => void
|
|
onNoteUpdate?: (text: string) => void
|
|
onHeightChange?: (height: number) => void
|
|
}
|
|
|
|
let {
|
|
summary,
|
|
selected = false,
|
|
stepCount,
|
|
color,
|
|
note,
|
|
showNote = false,
|
|
editMode = false,
|
|
modules,
|
|
onExpand,
|
|
onSummaryUpdate,
|
|
onNoteUpdate,
|
|
onHeightChange
|
|
}: Props = $props()
|
|
|
|
let noteColorConfig = $derived(
|
|
color ? (NOTE_COLORS[color as NoteColor] ?? NOTE_COLORS[NoteColor.BLUE]) : undefined
|
|
)
|
|
|
|
let defaultColorClasses = $derived(getNodeColorClasses(undefined, selected))
|
|
|
|
// Inline summary editing
|
|
let editingSummary = $state(false)
|
|
let summaryInput = $state('')
|
|
let textInputComponent: TextInput | undefined = $state(undefined)
|
|
|
|
function startEditingSummary() {
|
|
if (!editMode) return
|
|
editingSummary = true
|
|
summaryInput = summary ?? ''
|
|
requestAnimationFrame(() => {
|
|
textInputComponent?.focus()
|
|
textInputComponent?.select()
|
|
})
|
|
}
|
|
|
|
function saveSummary() {
|
|
editingSummary = false
|
|
const trimmed = summaryInput.trim()
|
|
if (trimmed !== (summary ?? '')) {
|
|
onSummaryUpdate?.(trimmed)
|
|
}
|
|
}
|
|
|
|
function handleSummaryKeydown(event: KeyboardEvent) {
|
|
if (event.key === 'Enter') {
|
|
saveSummary()
|
|
} else if (event.key === 'Escape') {
|
|
editingSummary = false
|
|
}
|
|
}
|
|
|
|
// Reset height to 0 when note is hidden
|
|
$effect(() => {
|
|
if (!showNote) {
|
|
onHeightChange?.(0)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<div
|
|
class={twMerge(
|
|
'w-full module cursor-pointer max-w-full',
|
|
'shadow-sm rounded-md overflow-clip',
|
|
'bg-surface-tertiary'
|
|
)}
|
|
style="width: {NODE.width}px;"
|
|
>
|
|
<div
|
|
class={twMerge(
|
|
'absolute z-0 outline-offset-0 inset-0',
|
|
'rounded-md',
|
|
noteColorConfig ? noteColorConfig.outline : defaultColorClasses.outline
|
|
)}
|
|
></div>
|
|
<div class="flex items-center w-full gap-1.5 px-2 h-[34px] relative z-1">
|
|
{#if modules && modules.length > 0}
|
|
<GroupModuleIcons {modules} />
|
|
{:else}
|
|
<Group size={14} />
|
|
{/if}
|
|
<div
|
|
class="absolute inset-x-0 flex items-center justify-center h-[34px] pointer-events-none px-8"
|
|
>
|
|
{#if editingSummary}
|
|
<TextInput
|
|
bind:this={textInputComponent}
|
|
bind:value={summaryInput}
|
|
size="xs"
|
|
class="!bg-transparent !border-transparent !shadow-none !text-2xs !font-medium !p-0 !m-0 !min-w-0 w-full text-center !min-h-0 !h-auto nodrag nowheel pointer-events-auto"
|
|
inputProps={{
|
|
placeholder: 'Group',
|
|
onblur: saveSummary,
|
|
onkeydown: handleSummaryKeydown,
|
|
spellcheck: false
|
|
}}
|
|
/>
|
|
{:else}
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<span
|
|
class="text-2xs font-medium truncate text-center pointer-events-auto {editMode
|
|
? 'cursor-text rounded px-0.5 -mx-0.5 hover:bg-black/10 dark:hover:bg-white/10'
|
|
: ''}"
|
|
onclick={editMode ? stopPropagation(preventDefault(startEditingSummary)) : undefined}
|
|
onpointerdown={editMode ? stopPropagation(preventDefault(() => {})) : undefined}
|
|
>{summary || 'Group'}</span
|
|
>
|
|
{/if}
|
|
</div>
|
|
<div class="flex-1"></div>
|
|
{#if stepCount != null}
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<span
|
|
class="text-3xs opacity-60 shrink-0 whitespace-nowrap {noteColorConfig
|
|
? noteColorConfig.text
|
|
: ''} {onExpand
|
|
? 'cursor-pointer hover:opacity-100 hover:text-blue-500 dark:hover:text-blue-400'
|
|
: ''}"
|
|
onclick={onExpand ? stopPropagation(preventDefault(onExpand)) : undefined}
|
|
>{stepCount} node{stepCount !== 1 ? 's' : ''}</span
|
|
>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if showNote}
|
|
<div class="relative z-1">
|
|
<GroupNoteArea
|
|
note={note ?? ''}
|
|
{color}
|
|
{editMode}
|
|
onHeightChange={(h) => onHeightChange?.(h)}
|
|
onNoteUpdate={(text) => onNoteUpdate?.(text)}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
</div>
|