mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-12 00:06:14 +00:00
reserve more fixed height for unsaved-changes banner to avoid content shift (#9873)
* fix(frontend): reserve fixed height for unsaved-changes banner to avoid content shift Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): remove border around reserved banner slot and shrink it Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): tighten top padding under the unsaved-changes banner Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): give unsaved-changes banner buttons minimal vertical breathing room Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(frontend): drop redundant Metadata section title in trigger and script editors Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): tuck schedule editor labels under summary to match convention Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * revert(frontend): keep Metadata section title in ScriptBuilder for a separate PR Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): drop leftover header-content margin on headless Section Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): drop top padding above resource editor first field Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(frontend): match variable editor bottom padding to resource Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): add Path label in new resource form to match edit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): reserve half the banner height to halve the idle gap Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): reserve a third of the banner height when idle Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): don't reserve banner slot or tighten top for new entities Gate the reserved-height slot and the tight content top padding on the banner's baseline (bannerReserved) instead of merely on the banner snippet being present, so new-entity drawers keep normal top spacing and add no empty slot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(frontend): trim banner comments to the 4-line invariant limit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(frontend): describe partial-reserve banner behavior accurately Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b92a86b8b3
commit
9c758df5eb
@@ -991,13 +991,15 @@
|
||||
</div>
|
||||
{:else if step == 2 && manual}
|
||||
<div class="flex flex-col gap-8">
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
initialPath=""
|
||||
namePlaceholder={resourceType}
|
||||
kind="resource"
|
||||
/>
|
||||
<Label label="Path">
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
initialPath=""
|
||||
namePlaceholder={resourceType}
|
||||
kind="resource"
|
||||
/>
|
||||
</Label>
|
||||
<LabelsInput bind:labels class="-mt-5" />
|
||||
{#if deployTo}
|
||||
<Label
|
||||
@@ -1247,13 +1249,15 @@
|
||||
<span class="text-xs text-primary font-normal"> Finish connection in popup window </span>
|
||||
{/if}
|
||||
{:else}
|
||||
<Path
|
||||
initialPath=""
|
||||
namePlaceholder={resourceType}
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
kind="resource"
|
||||
/>
|
||||
<Label label="Path">
|
||||
<Path
|
||||
initialPath=""
|
||||
namePlaceholder={resourceType}
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
kind="resource"
|
||||
/>
|
||||
</Label>
|
||||
<LabelsInput bind:labels class="-mt-5" />
|
||||
{#if deployTo}
|
||||
<Label
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
} from '$lib/utils'
|
||||
import { AlertCircle, Diff } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
interface Props {
|
||||
/** Whether there are unsaved local changes relative to the deployed baseline. */
|
||||
@@ -25,6 +25,12 @@
|
||||
disabled?: boolean
|
||||
/** Diff drawer title. */
|
||||
title?: string
|
||||
/**
|
||||
* Whether the banner can appear, so it partially reserves its slot to keep
|
||||
* the shift small. Pass when `getDeployed()` is non-reactive; else defaults
|
||||
* to `getDeployed() != null`.
|
||||
*/
|
||||
reserveSpace?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -33,7 +39,8 @@
|
||||
getCurrent,
|
||||
onDiscard,
|
||||
disabled = false,
|
||||
title = 'Deployed <> Local changes'
|
||||
title = 'Deployed <> Local changes',
|
||||
reserveSpace
|
||||
}: Props = $props()
|
||||
|
||||
/** Same cleaning + YAML serialization the DiffDrawer applies before
|
||||
@@ -49,6 +56,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the banner can appear (a deployed baseline exists). Gates the
|
||||
// partially-reserved slot below (keeps the toggle shift small); new entities
|
||||
// have none, so no slot and no gap.
|
||||
let hasBaseline = $derived(reserveSpace ?? getDeployed() != null)
|
||||
|
||||
// Suppress the banner when:
|
||||
// • There's no deployed baseline (brand-new entity — "Show diff"
|
||||
// would early-return and "Discard" is semantically backwards),
|
||||
@@ -58,10 +70,8 @@
|
||||
// from baseline" check that can stale-fire after a save lands
|
||||
// or when `false`/`undefined` toggle noise flips a field.
|
||||
let visible = $derived.by(() => {
|
||||
if (!show) return false
|
||||
const deployed = getDeployed()
|
||||
if (deployed == null) return false
|
||||
return diffKey(deployed) !== diffKey(getCurrent())
|
||||
if (!show || !hasBaseline) return false
|
||||
return diffKey(getDeployed()) !== diffKey(getCurrent())
|
||||
})
|
||||
|
||||
let diffDrawer: DiffDrawer | undefined = $state()
|
||||
@@ -99,37 +109,44 @@
|
||||
|
||||
<DiffDrawer bind:this={diffDrawer} />
|
||||
|
||||
{#if visible}
|
||||
<div
|
||||
transition:slide|local={{ duration: 120 }}
|
||||
class={twMerge(
|
||||
'flex flex-row items-center justify-between gap-2 px-4 py-1',
|
||||
classes.warning.bgClass,
|
||||
'!border-0 !rounded-none'
|
||||
)}
|
||||
>
|
||||
<div class="flex flex-row items-center gap-2 min-w-0">
|
||||
<AlertCircle class={classes.warning.iconClass} size={16} />
|
||||
<span class={twMerge('text-xs font-semibold truncate', classes.warning.titleClass)}>
|
||||
You have unsaved changes
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2 shrink-0">
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="default"
|
||||
startIcon={{ icon: Diff }}
|
||||
btnClasses={classes.warning.titleClass}
|
||||
on:click={showDiff}>Show diff</Button
|
||||
{#if hasBaseline}
|
||||
<!-- Reserve ~a third of the banner height when idle (h-2.5), grow to the full
|
||||
h-8 when it shows: small idle gap, ~22px animated shift on appear. h-8 =
|
||||
button height + a couple px so the buttons don't touch the edges. -->
|
||||
<div class={twMerge('shrink-0 transition-[height] duration-150', visible ? 'h-8' : 'h-2.5')}>
|
||||
{#if visible}
|
||||
<div
|
||||
transition:fade|local={{ duration: 120 }}
|
||||
class={twMerge(
|
||||
'flex flex-row items-center justify-between gap-2 px-4 h-full',
|
||||
classes.warning.bgClass,
|
||||
'!border-0 !rounded-none'
|
||||
)}
|
||||
>
|
||||
{#if !disabled}
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="subtle"
|
||||
btnClasses={classes.warning.titleClass}
|
||||
on:click={onDiscard}>Discard</Button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2 min-w-0">
|
||||
<AlertCircle class={classes.warning.iconClass} size={16} />
|
||||
<span class={twMerge('text-xs font-semibold truncate', classes.warning.titleClass)}>
|
||||
You have unsaved changes
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2 shrink-0">
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="default"
|
||||
startIcon={{ icon: Diff }}
|
||||
btnClasses={classes.warning.titleClass}
|
||||
on:click={showDiff}>Show diff</Button
|
||||
>
|
||||
{#if !disabled}
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="subtle"
|
||||
btnClasses={classes.warning.titleClass}
|
||||
on:click={onDiscard}>Discard</Button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -374,7 +374,7 @@
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="flex flex-col gap-6 py-2">
|
||||
<div class="flex flex-col gap-6 pb-2">
|
||||
{#if otherDirty.length > 0}
|
||||
<Alert type="warning" title="Editing multiple workspaces">
|
||||
You are going to edit the value in: {otherDirty.join(', ')}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<Drawer bind:this={drawer} size="50rem" {disableChatOffset}>
|
||||
<DrawerContent
|
||||
title={mode == 'edit' ? 'Edit ' + path : 'Add a resource'}
|
||||
bannerReserved={mode == 'edit'}
|
||||
on:close={drawer?.closeDrawer}
|
||||
>
|
||||
{#await import('./ResourceEditor.svelte')}
|
||||
@@ -79,6 +80,7 @@
|
||||
{#snippet banner()}
|
||||
<LocalDraftBanner
|
||||
show={hasLocalDraft}
|
||||
reserveSpace={mode == 'edit'}
|
||||
getDeployed={() => resourceEditor?.localDraftDeployed()}
|
||||
getCurrent={() => resourceEditor?.localDraftCurrent()}
|
||||
onDiscard={() => resourceEditor?.discardLocalDraft()}
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
{#if description}
|
||||
<div class="text-xs text-primary mt-1 mb-2">{@html description}</div>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-6 grow min-h-0 mt-4">
|
||||
<div class={twMerge('flex flex-col gap-6 grow min-h-0', headless ? '' : 'mt-4')}>
|
||||
<div class={twMerge('grow min-h-0', clazz)}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -292,11 +292,13 @@
|
||||
<Drawer bind:this={drawer} size="50rem">
|
||||
<DrawerContent
|
||||
title={edit ? `Update variable at ${initialPath}` : 'Add a variable'}
|
||||
bannerReserved={edit}
|
||||
on:close={drawer?.closeDrawer}
|
||||
>
|
||||
{#snippet banner()}
|
||||
<LocalDraftBanner
|
||||
show={edit && selectedDirty}
|
||||
reserveSpace={edit}
|
||||
getDeployed={() => (selected ? initialStates[selected] : undefined)}
|
||||
getCurrent={() => current}
|
||||
onDiscard={() => {
|
||||
@@ -308,7 +310,7 @@
|
||||
disabled={!can_write}
|
||||
/>
|
||||
{/snippet}
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex flex-col gap-8 pb-2">
|
||||
{#if !can_write}
|
||||
<Alert type="warning" title="Only read access">
|
||||
You only have read access to this resource and cannot edit it
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
titleExtra?: import('svelte').Snippet
|
||||
/** Rendered fixed below the header, above the scrollable content. */
|
||||
banner?: import('svelte').Snippet
|
||||
/**
|
||||
* Whether the `banner` reserves space (its baseline exists). Only then does
|
||||
* the content hug it with tight top padding; new entities keep normal padding.
|
||||
*/
|
||||
bannerReserved?: boolean
|
||||
children?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
@@ -43,6 +48,7 @@
|
||||
actions,
|
||||
titleExtra,
|
||||
banner,
|
||||
bannerReserved = false,
|
||||
children
|
||||
}: Props = $props()
|
||||
|
||||
@@ -86,19 +92,29 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if banner}
|
||||
{@render banner()}
|
||||
{/if}
|
||||
{#snippet contentBox(tightTop = false)}
|
||||
<div
|
||||
class={classNames(
|
||||
noPadding ? '' : tightTop ? 'px-4 pb-4 pt-1' : 'p-4',
|
||||
'grow min-h-0 max-h-full',
|
||||
forceOverflowVisible ? '!overflow-visible' : ''
|
||||
)}
|
||||
class:overflow-y-auto={overflow_y}
|
||||
style={overflow_y ? 'scrollbar-gutter: stable;' : ''}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
noPadding ? '' : 'p-4',
|
||||
'grow min-h-0 max-h-full',
|
||||
forceOverflowVisible ? '!overflow-visible' : ''
|
||||
)}
|
||||
class:overflow-y-auto={overflow_y}
|
||||
style={overflow_y ? 'scrollbar-gutter: stable;' : ''}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{#if banner}
|
||||
<!-- Banner + content share one `divide-y` cell: the header keeps its single
|
||||
divider (no bordered strip around the reserved slot) and the content hugs
|
||||
the slot with tight top padding (see `contentBox`/`bannerReserved`). -->
|
||||
<div class="flex flex-col grow min-h-0 max-h-full">
|
||||
{@render banner()}
|
||||
{@render contentBox(bannerReserved)}
|
||||
</div>
|
||||
{:else}
|
||||
{@render contentBox()}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -350,6 +350,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit Azure trigger ${initialPath}`
|
||||
@@ -364,6 +365,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -368,7 +368,7 @@
|
||||
{#if mode === 'suspended'}
|
||||
<TriggerSuspendedJobsAlert {suspendedJobsModal} />
|
||||
{/if}
|
||||
<Section label="Metadata">
|
||||
<Section headless>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label label="Path">
|
||||
<Path
|
||||
@@ -475,6 +475,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="700px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit email trigger ${initialPath}`
|
||||
@@ -489,6 +490,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -367,6 +367,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit GCP Pub/Sub trigger ${initialPath}`
|
||||
@@ -381,6 +382,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -535,7 +535,7 @@
|
||||
{#if mode === 'suspended'}
|
||||
<TriggerSuspendedJobsAlert {suspendedJobsModal} />
|
||||
{/if}
|
||||
<Section label="Metadata">
|
||||
<Section headless>
|
||||
<div class="flex flex-col gap-6">
|
||||
<Label label="Summary" for="summary">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
@@ -1005,6 +1005,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="700px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit route ${initialPath}`
|
||||
@@ -1019,6 +1020,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -416,6 +416,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit Kafka trigger ${initialPath}`
|
||||
@@ -430,6 +431,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -391,6 +391,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit MQTT trigger ${initialPath}`
|
||||
@@ -405,6 +406,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -395,7 +395,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col gap-12 mt-6">
|
||||
<Section label="Metadata">
|
||||
<Section headless>
|
||||
<div class="flex flex-col gap-6">
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs font-semibold text-emphasis">Summary</span>
|
||||
|
||||
@@ -388,6 +388,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit NATS trigger ${initialPath}`
|
||||
@@ -402,6 +403,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -566,6 +566,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit Postgres trigger ${initialPath}`
|
||||
@@ -578,6 +579,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -781,7 +781,7 @@
|
||||
}}
|
||||
/>
|
||||
<div class="flex flex-col gap-8">
|
||||
<Section label="Metadata">
|
||||
<Section headless>
|
||||
<div class="flex flex-col gap-6">
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs font-semibold text-emphasis">Summary</span>
|
||||
@@ -807,7 +807,7 @@
|
||||
bind:value={summary}
|
||||
/>
|
||||
</label>
|
||||
<LabelsInput bind:labels />
|
||||
<LabelsInput bind:labels class="-mt-4" />
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="path" class="text-xs font-semibold text-emphasis">Path</label>
|
||||
@@ -1380,6 +1380,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="900px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit schedule ${initialPath}`
|
||||
@@ -1396,6 +1397,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -370,6 +370,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit SQS trigger ${initialPath}`
|
||||
@@ -384,6 +385,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
@@ -48,6 +48,11 @@ export interface TriggerDraftSync {
|
||||
* loading and for brand-new triggers (no deployed baseline yet).
|
||||
*/
|
||||
readonly hasDraft: boolean
|
||||
/**
|
||||
* Whether a deployed baseline exists (banner can appear). Reactive, unlike
|
||||
* reading `deployed` directly (a plain `let`), so callers can reserve its slot.
|
||||
*/
|
||||
readonly hasBaseline: boolean
|
||||
/** The deployed baseline the dirty check compares against. */
|
||||
readonly deployed: Cfg | undefined
|
||||
/** The current form config (the live local draft). */
|
||||
@@ -104,6 +109,10 @@ export function useTriggerDraftSync(opts: TriggerDraftSyncOptions): TriggerDraft
|
||||
cfgDiffers(opts.getCfg() as Cfg, opts.deployed() as Cfg)
|
||||
)
|
||||
|
||||
// Reactive "banner is possible" — depends on `drawerLoading()` so it
|
||||
// re-evaluates (and re-reads the plain-`let` baseline) once the load settles.
|
||||
const hasBaseline = $derived(!opts.drawerLoading() && opts.deployed() != null)
|
||||
|
||||
/** `auto: true` marks a discard from the reactive persist-effect (not an
|
||||
* explicit user action), so it respects the "Enable auto-save" toggle — else
|
||||
* with autosave off the editor would delete server drafts while writing none. */
|
||||
@@ -189,6 +198,9 @@ export function useTriggerDraftSync(opts: TriggerDraftSyncOptions): TriggerDraft
|
||||
get hasDraft() {
|
||||
return hasDraft
|
||||
},
|
||||
get hasBaseline() {
|
||||
return hasBaseline
|
||||
},
|
||||
get deployed() {
|
||||
return opts.deployed()
|
||||
},
|
||||
|
||||
@@ -457,6 +457,7 @@
|
||||
{#if useDrawer}
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
bannerReserved={draftSync.hasBaseline}
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit WebSocket trigger ${initialPath}`
|
||||
@@ -471,6 +472,7 @@
|
||||
<LocalDraftBanner
|
||||
show={draftSync.hasDraft}
|
||||
getDeployed={() => draftSync.deployed}
|
||||
reserveSpace={draftSync.hasBaseline}
|
||||
getCurrent={() => draftSync.current}
|
||||
onDiscard={() => draftSync.resetToDeployed(initialPath)}
|
||||
disabled={!can_write}
|
||||
|
||||
Reference in New Issue
Block a user