diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index 62b457d5b6..26a9f409e7 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -308,19 +308,33 @@ } } - type PendingDeleteConfirmation = { - plan: DeletePlan - } - - type PendingGroupAction = { - groups: FlowGroup[] + // A single delete can have several consequences at once (emptied groups *and* + // dependent steps); they are confirmed together so one user action never raises + // more than one dialog. + type PendingModuleAction = { label: 'delete' | 'move' + stepCount: number + groups: FlowGroup[] + dependents: Record confirm: () => void cancel?: () => void } - let pendingDeleteConfirmation: PendingDeleteConfirmation | undefined = $state(undefined) - let pendingGroupAction: PendingGroupAction | undefined = $state(undefined) + // The modal keeps rendering `pendingModuleAction` while it fades out, so visibility is + // driven by `moduleActionOpen` rather than by clearing the value — clearing it would + // blank the dialog mid-transition. `moduleActionOpen` also makes confirm/cancel + // one-shot: the buttons stay clickable until the fade ends. + let pendingModuleAction: PendingModuleAction | undefined = $state(undefined) + let moduleActionOpen = $state(false) + + function askModuleAction(action: PendingModuleAction) { + pendingModuleAction = action + moduleActionOpen = true + } + + function stepNoun(action: PendingModuleAction | undefined) { + return (action?.stepCount ?? 1) > 1 ? 'steps' : 'step' + } let graph: FlowGraphV2 | undefined = $state(undefined) let noteMode = $state(false) @@ -363,23 +377,20 @@ return } - const proceed = () => { - if (request.needsDependencyConfirmation) { - pendingDeleteConfirmation = { plan: request.plan } - } else { - applyDeletePlan(request.plan) - } + const affectedGroups = request.plan.structureDelete?.affectedGroups ?? [] + + if (affectedGroups.length === 0 && !request.needsDependencyConfirmation) { + applyDeletePlan(request.plan) + return } - if ((request.plan.structureDelete?.affectedGroups.length ?? 0) > 0) { - pendingGroupAction = { - groups: request.plan.structureDelete!.affectedGroups, - label: 'delete', - confirm: proceed - } - } else { - proceed() - } + askModuleAction({ + label: 'delete', + stepCount: request.plan.targets.length, + groups: affectedGroups, + dependents: request.plan.dependents, + confirm: () => applyDeletePlan(request.plan) + }) } export function deleteMultiple(ids: string[]) { @@ -495,61 +506,58 @@ { - if (pendingDeleteConfirmation) { - applyDeletePlan(pendingDeleteConfirmation.plan) - pendingDeleteConfirmation = undefined - } + if (!moduleActionOpen) return + moduleActionOpen = false + pendingModuleAction?.confirm() }} on:canceled={() => { - pendingDeleteConfirmation = undefined + if (!moduleActionOpen) return + moduleActionOpen = false + pendingModuleAction?.cancel?.() }} > -
Found the following steps that will require changes after this step is deleted:
- {#each Object.entries(pendingDeleteConfirmation?.plan.dependents ?? {}) as [k, v]} -
-

{k}

-
    - {#each v as dep} -
  • {dep}
  • + {#if pendingModuleAction} + {@const action = pendingModuleAction} + {@const dependents = Object.entries(action.dependents)} + {#if action.groups.length === 1} + {@const group = action.groups[0]} +

    The group{group.summary ? ` "${group.summary}"` : ''} will be removed (empty or duplicate).

    + {:else if action.groups.length > 1} +

    The following groups will be removed (empty or duplicate):

    +
      + {#each action.groups as group} +
    • {group.summary || `${group.start_id} → ${group.end_id}`}
    • {/each}
    -
- {/each} -
- - { - pendingGroupAction?.confirm() - pendingGroupAction = undefined - }} - on:canceled={() => { - pendingGroupAction?.cancel?.() - pendingGroupAction = undefined - }} - > - {#if pendingGroupAction?.groups.length === 1} - {@const group = pendingGroupAction.groups[0]} -

The group{group.summary ? ` "${group.summary}"` : ''} will be removed (empty or duplicate). - Are you sure you want to {pendingGroupAction.label} the step?

- {:else} -

The following groups will be removed (empty or duplicate):

-
    - {#each pendingGroupAction?.groups ?? [] as group} -
  • {group.summary || `${group.start_id} → ${group.end_id}`}
  • - {/each} -
-

Are you sure you want to {pendingGroupAction?.label} the step?

+ {/if} + {#if dependents.length > 0} +

0 ? 'mt-3' : ''} + >The following steps will require changes afterwards:

+
+ {#each dependents as [k, v]} +
+

{k}

+
    + {#each v as dep} +
  • {dep}
  • + {/each} +
+
+ {/each} +
+ {/if} +

Are you sure you want to {action.label} the {stepNoun(action)}?

{/if}
@@ -692,12 +700,14 @@ } if (affectedGroups.length > 0) { - pendingGroupAction = { - groups: affectedGroups, + askModuleAction({ label: 'move', + stepCount: movedIds.length, + groups: affectedGroups, + dependents: {}, confirm: doMove, cancel: () => moveManager.clearMoving() - } + }) } else { doMove() }