mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-27 16:02:43 +00:00
fix: the widest action rows scroll, and the canvas toggles show only where they fit
Measuring the band instead of estimating it: with the canvas toggles shown the app editor's actions are 826-911px and the trail with the summary another ~500, so the row overflowed the bar at every width up to about 1470 — the 1100 I set from an estimate was wrong, and the 1350 before it was too. The toggles come out at 1500 now, and the menu entries are what keeps all three reachable below that rather than merely out of the way. The two widest rows — this editor's and the pipeline's — scroll inside their own box, which is the escape the rows they replaced had (`overflow-x-auto whitespace-nowrap`). That answers pi's finding and clears the last 23-40px of band overflow at middling widths, which predated the conversion. Measured after: zero horizontal overflow from 852px of bar to 1392. From the CI round's other findings: - The pen is offered when either the summary or the path is editable, not the path alone: a host that allowed one and refused the other could no longer edit the summary from the band. `PathEditPopover` takes `pathEditable` to match, and its label names whichever it is given. - The four editors' comments called the summary read-only, which stopped being true when the pen beside it started editing it. - The pipeline's "centre group" comment described a layout it no longer has. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
72e678a923
commit
1dbe7bf9af
@@ -1654,9 +1654,8 @@
|
||||
{/if}
|
||||
|
||||
{#snippet flowSummary()}
|
||||
<!-- Read-only here: the summary is a Settings-tab field, and the band is where the flow
|
||||
is named rather than where it is edited. The pen sits after it, at the end of what
|
||||
it renames. -->
|
||||
<!-- Not edited in place: the pen beside it opens the summary and the path together,
|
||||
so the band reads as a name rather than a form. `title` for one it truncates. -->
|
||||
<div class="group flex items-center gap-1 min-w-0">
|
||||
<span
|
||||
class="min-w-0 truncate text-xs {emptyString(flowStore.val.summary)
|
||||
@@ -1665,11 +1664,12 @@
|
||||
title={flowStore.val.summary}
|
||||
>{emptyString(flowStore.val.summary) ? 'Add a summary...' : flowStore.val.summary}</span
|
||||
>
|
||||
{#if customUi?.topBar?.editablePath != false}
|
||||
{#if customUi?.topBar?.editablePath != false || customUi?.topBar?.editableSummary != false}
|
||||
<PathEditPopover
|
||||
penVisibility={emptyString(flowStore.val.summary) ? 'always' : 'hover'}
|
||||
bind:summary={flowStore.val.summary}
|
||||
summaryEditable={customUi?.topBar?.editableSummary != false}
|
||||
pathEditable={customUi?.topBar?.editablePath != false}
|
||||
bind:path={$pathStore}
|
||||
bind:snapshotPath={pathSnapshot}
|
||||
savedPath={initialPath}
|
||||
|
||||
@@ -30,6 +30,9 @@ to it — does not reflow under the pointer as the user types, which floating-ui
|
||||
summary?: string
|
||||
/** When false the summary is shown but not editable, for a host whose `customUi` says so. */
|
||||
summaryEditable?: boolean
|
||||
/** When false the path is shown but not editable, the same way. A host may allow one and
|
||||
* refuse the other, and the pen offers whichever it is given. */
|
||||
pathEditable?: boolean
|
||||
path?: string
|
||||
/** The item's *saved* path on the server, so the popover can say a rename needs deploying. */
|
||||
savedPath?: string
|
||||
@@ -47,6 +50,7 @@ to it — does not reflow under the pointer as the user types, which floating-ui
|
||||
let {
|
||||
summary = $bindable(),
|
||||
summaryEditable = true,
|
||||
pathEditable = true,
|
||||
path = $bindable(),
|
||||
savedPath,
|
||||
kind = 'flow',
|
||||
@@ -57,10 +61,17 @@ to it — does not reflow under the pointer as the user types, which floating-ui
|
||||
}: Props = $props()
|
||||
|
||||
let open = $state(false)
|
||||
const penLabel = $derived(
|
||||
summaryEditable && pathEditable
|
||||
? 'Edit summary and path'
|
||||
: summaryEditable
|
||||
? 'Edit summary'
|
||||
: 'Edit path'
|
||||
)
|
||||
|
||||
// Treat an empty path as ownable so the popover lets a user pick the path for a brand-new
|
||||
// item. `Path.reset()` then synthesizes a default under their own user/folder scope.
|
||||
const own = $derived(!path || isOwner(path, actingUser, $operatingWorkspace))
|
||||
const own = $derived(pathEditable && (!path || isOwner(path, actingUser, $operatingWorkspace)))
|
||||
|
||||
function setOpen(v: boolean) {
|
||||
open = v
|
||||
@@ -86,8 +97,8 @@ to it — does not reflow under the pointer as the user types, which floating-ui
|
||||
unifiedSize="xs"
|
||||
iconOnly
|
||||
startIcon={{ icon: Pencil }}
|
||||
title={summaryEditable ? 'Edit summary and path' : 'Edit path'}
|
||||
aria-label={summaryEditable ? 'Edit summary and path' : 'Edit path'}
|
||||
title={penLabel}
|
||||
aria-label={penLabel}
|
||||
btnClasses={penVisibility === 'hover' && !open
|
||||
? 'opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100'
|
||||
: ''}
|
||||
@@ -142,7 +153,11 @@ to it — does not reflow under the pointer as the user types, which floating-ui
|
||||
{:else}
|
||||
<Label label="Path">
|
||||
<span class="text-xs font-mono text-secondary">{path}</span>
|
||||
<p class="text-2xs text-tertiary mt-1">Only the owner can change the path</p>
|
||||
<p class="text-2xs text-tertiary mt-1">
|
||||
{pathEditable
|
||||
? 'Only the owner can change the path'
|
||||
: 'This path cannot be changed here'}
|
||||
</p>
|
||||
</Label>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -2338,9 +2338,8 @@
|
||||
{/if}
|
||||
|
||||
{#snippet scriptSummary()}
|
||||
<!-- Read-only here: the summary is a settings field, and the band is where the script is
|
||||
named rather than where it is edited. The pen sits after it, at the end of what it
|
||||
renames. -->
|
||||
<!-- Not edited in place: the pen beside it opens the summary and the path together,
|
||||
so the band reads as a name rather than a form. `title` for one it truncates. -->
|
||||
<div class="group flex items-center gap-1 min-w-0">
|
||||
<span
|
||||
class="min-w-0 truncate text-xs {emptyString(script.summary)
|
||||
@@ -2349,11 +2348,12 @@
|
||||
title={script.summary}
|
||||
>{emptyString(script.summary) ? 'Add a summary...' : script.summary}</span
|
||||
>
|
||||
{#if customUi?.topBar?.editablePath != false}
|
||||
{#if customUi?.topBar?.editablePath != false || customUi?.topBar?.editableSummary != false}
|
||||
<PathEditPopover
|
||||
penVisibility={emptyString(script.summary) ? 'always' : 'hover'}
|
||||
bind:summary={script.summary}
|
||||
summaryEditable={customUi?.topBar?.editableSummary != false}
|
||||
pathEditable={customUi?.topBar?.editablePath != false}
|
||||
bind:path={script.path}
|
||||
bind:snapshotPath={pathSnapshot}
|
||||
savedPath={initialPath}
|
||||
|
||||
@@ -264,11 +264,12 @@
|
||||
// In the page header the buttons share the row with the breadcrumb, so they shed the canvas
|
||||
// toggles and fold Debug runs into the menu sooner: the whole group is ~840px and the trail
|
||||
// with the summary takes ~430px of the same row.
|
||||
// Measured on the band: the canvas toggles are 240px, the rest of the actions 334, and the trail
|
||||
// with the summary about 430 — so the whole row fits down to ~1050. 1350 hid the toggles on an
|
||||
// ordinary laptop (a 1440 window with the rail out leaves 1232), and the menu below is what
|
||||
// keeps them reachable under this.
|
||||
const compactBelow = $derived(ownsPageHeader ? 1100 : 720)
|
||||
// Measured on the band rather than estimated: with the canvas toggles shown the actions alone
|
||||
// are 826-911px and the trail with the summary another ~500, so the row overflows the bar at
|
||||
// every width up to about 1470 — 1350 was not high enough either. Below this the toggles stand
|
||||
// down and the menu carries all three, which is what keeps them reachable rather than merely
|
||||
// out of the way.
|
||||
const compactBelow = $derived(ownsPageHeader ? 1500 : 720)
|
||||
const compactTopbar = $derived.by(() => {
|
||||
const w = ownsPageHeader ? pageHeader.barWidth : topbarWidth
|
||||
return w > 0 && w < compactBelow
|
||||
@@ -1020,9 +1021,8 @@
|
||||
{/if}
|
||||
|
||||
{#snippet appSummary()}
|
||||
<!-- Read-only here: the summary is a deploy-drawer field, and the band is where the app is
|
||||
named rather than where it is edited. `title` so one the band truncates can still be read.
|
||||
The pen sits after it, at the end of what it renames. -->
|
||||
<!-- Not edited in place: the pen beside it opens the summary and the path together,
|
||||
so the band reads as a name rather than a form. `title` for one it truncates. -->
|
||||
<div class="group flex items-center gap-1 min-w-0">
|
||||
<span
|
||||
class="min-w-0 truncate text-xs {emptyString($summary)
|
||||
@@ -1043,14 +1043,20 @@
|
||||
|
||||
{#snippet appHeaderActions()}
|
||||
<!-- The bar's own order kept: what the canvas looks like, how the draft is doing, which panels
|
||||
are open, then what to do with the app. -->
|
||||
{@render canvasToggles()}
|
||||
{@render autosaveIndicator()}
|
||||
{#if !phoneTopbar}
|
||||
{@render panelButtons()}
|
||||
{@render awarenessMark()}
|
||||
{/if}
|
||||
{@render appActions()}
|
||||
are open, then what to do with the app. The set scrolls rather than pushing past the end of
|
||||
the bar — this editor carries the widest action row of any page, and the row it replaced
|
||||
scrolled for the same reason. -->
|
||||
<div
|
||||
class="flex flex-row items-center gap-2 min-w-0 overflow-x-auto scrollbar-hidden whitespace-nowrap"
|
||||
>
|
||||
{@render canvasToggles()}
|
||||
{@render autosaveIndicator()}
|
||||
{#if !phoneTopbar}
|
||||
{@render panelButtons()}
|
||||
{@render awarenessMark()}
|
||||
{/if}
|
||||
{@render appActions()}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet canvasToggles()}
|
||||
|
||||
@@ -1021,9 +1021,8 @@
|
||||
{/snippet}
|
||||
|
||||
{#snippet appSummary()}
|
||||
<!-- Read-only here: the summary is a deploy-drawer field, and the band is where the app is
|
||||
named rather than where it is edited. `title` so one the band truncates can still be read.
|
||||
The pen sits after it, at the end of what it renames. -->
|
||||
<!-- Not edited in place: the pen beside it opens the summary and the path together,
|
||||
so the band reads as a name rather than a form. `title` for one it truncates. -->
|
||||
<div class="group flex items-center gap-1 min-w-0">
|
||||
<span
|
||||
class="min-w-0 truncate text-xs {emptyString(summary)
|
||||
|
||||
@@ -2392,6 +2392,7 @@
|
||||
section={{ label: mode === 'edit' ? 'Pipeline editor' : 'Pipeline' }}
|
||||
afterName={pipelineFolder}
|
||||
actions={pipelineActions}
|
||||
actionsFlexible
|
||||
/>
|
||||
|
||||
{#snippet pipelineFolder()}
|
||||
@@ -2437,154 +2438,159 @@
|
||||
{/snippet}
|
||||
|
||||
{#snippet pipelineActions()}
|
||||
{#if !isOperator}
|
||||
<!-- Center group: the mode toggle is the page's primary control —
|
||||
anchored between the two flex-1 side groups so it stays
|
||||
centered, with breathing room on both sides. -->
|
||||
<div class="flex flex-row items-center gap-2 shrink-0 px-6">
|
||||
<PipelineModeToggle {mode} draftCount={pe.drafts.size} onModeChange={(m) => setMode(m)} />
|
||||
{#if mode === 'view' && pe.drafts.size > 0}
|
||||
<!-- View variant: overlay the unsaved drafts onto the deployed
|
||||
<!-- This page carries more actions than any other in the band, so the set scrolls rather than
|
||||
being pushed off the end — the same escape the row this replaced had. `actionsFlexible` on
|
||||
the registration is what lets this box shrink far enough for that to matter. -->
|
||||
<div
|
||||
class="flex flex-row items-center gap-2 min-w-0 overflow-x-auto scrollbar-hidden whitespace-nowrap"
|
||||
>
|
||||
{#if !isOperator}
|
||||
<!-- The mode toggle is the page's primary control, so it leads the set. -->
|
||||
<div class="flex flex-row items-center gap-2 shrink-0 pr-4">
|
||||
<PipelineModeToggle {mode} draftCount={pe.drafts.size} onModeChange={(m) => setMode(m)} />
|
||||
{#if mode === 'view' && pe.drafts.size > 0}
|
||||
<!-- View variant: overlay the unsaved drafts onto the deployed
|
||||
graph — "what View will show once they're deployed". -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (includeDrafts = !includeDrafts)}
|
||||
class={twMerge(
|
||||
'flex items-center gap-1.5 px-2.5 py-1 rounded-md border text-xs font-medium transition-colors',
|
||||
includeDrafts
|
||||
? 'bg-amber-50 dark:bg-amber-900/30 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400'
|
||||
: 'bg-surface border-gray-300 dark:border-gray-600 text-secondary hover:bg-surface-hover'
|
||||
)}
|
||||
title={includeDrafts
|
||||
? 'Showing undeployed drafts overlaid on the deployed pipeline — click to hide them'
|
||||
: 'Overlay your undeployed drafts to see what the pipeline will look like once deployed'}
|
||||
>
|
||||
<Telescope size={14} />
|
||||
{includeDrafts ? 'Showing' : 'Show'}
|
||||
{pe.drafts.size} draft{pe.drafts.size === 1 ? '' : 's'}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if !isOperator && allPipelineScripts.length > 0}
|
||||
<!-- Only the armed state surfaces on the bar (arming lives in the ⋮
|
||||
menu) — a compact disarm pill. -->
|
||||
{#if recordingMode}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (includeDrafts = !includeDrafts)}
|
||||
class={twMerge(
|
||||
'flex items-center gap-1.5 px-2.5 py-1 rounded-md border text-xs font-medium transition-colors',
|
||||
includeDrafts
|
||||
? 'bg-amber-50 dark:bg-amber-900/30 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400'
|
||||
: 'bg-surface border-gray-300 dark:border-gray-600 text-secondary hover:bg-surface-hover'
|
||||
)}
|
||||
title={includeDrafts
|
||||
? 'Showing undeployed drafts overlaid on the deployed pipeline — click to hide them'
|
||||
: 'Overlay your undeployed drafts to see what the pipeline will look like once deployed'}
|
||||
onclick={() => (recordingMode = false)}
|
||||
disabled={!!cascadeRunningRoot}
|
||||
class="flex items-center gap-1.5 px-2.5 py-1 rounded-md border text-xs font-medium transition-colors disabled:opacity-50 bg-red-50 dark:bg-red-900/30 border-red-300 dark:border-red-700 text-red-700 dark:text-red-400"
|
||||
title={RECORDING_ARMED_HINT}
|
||||
>
|
||||
<Telescope size={14} />
|
||||
{includeDrafts ? 'Showing' : 'Show'}
|
||||
{pe.drafts.size} draft{pe.drafts.size === 1 ? '' : 's'}
|
||||
<Circle size={12} class="fill-red-600 text-red-600 animate-pulse" />
|
||||
Recording
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if !isOperator && allPipelineScripts.length > 0}
|
||||
<!-- Only the armed state surfaces on the bar (arming lives in the ⋮
|
||||
menu) — a compact disarm pill. -->
|
||||
{#if recordingMode}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (recordingMode = false)}
|
||||
disabled={!!cascadeRunningRoot}
|
||||
class="flex items-center gap-1.5 px-2.5 py-1 rounded-md border text-xs font-medium transition-colors disabled:opacity-50 bg-red-50 dark:bg-red-900/30 border-red-300 dark:border-red-700 text-red-700 dark:text-red-400"
|
||||
title={RECORDING_ARMED_HINT}
|
||||
>
|
||||
<Circle size={12} class="fill-red-600 text-red-600 animate-pulse" />
|
||||
Recording
|
||||
</button>
|
||||
{/if}
|
||||
<!-- Pipeline-level run: always visible in both View and Edit so a
|
||||
<!-- Pipeline-level run: always visible in both View and Edit so a
|
||||
run never requires hunting for a specific node's play button
|
||||
(dbt `build` / Dagster "Materialize all"). Runs every script in
|
||||
dependency order (roots first, cascading downstream) over the
|
||||
displayed graph — deployed in View, draft-overlaid in Edit. -->
|
||||
<Button
|
||||
variant="accent-secondary"
|
||||
unifiedSize="sm"
|
||||
startIcon={{
|
||||
icon: cascadeRunningRoot ? Loader2 : Play,
|
||||
classes: cascadeRunningRoot ? 'animate-spin' : undefined
|
||||
}}
|
||||
onclick={runWholePipeline}
|
||||
disabled={!!cascadeRunningRoot}
|
||||
title={cascadeRunningRoot
|
||||
? 'A pipeline run is already in progress'
|
||||
: `Run all ${allPipelineScripts.length} script${
|
||||
allPipelineScripts.length === 1 ? '' : 's'
|
||||
} in dependency order (roots first, cascading downstream)`}
|
||||
>
|
||||
{cascadeRunningRoot ? 'Running…' : 'Run pipeline'}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if mode === 'edit' && saveErrors.size > 0}
|
||||
<!-- Compact errors popover anchored next to Save all so users
|
||||
<Button
|
||||
variant="accent-secondary"
|
||||
unifiedSize="sm"
|
||||
startIcon={{
|
||||
icon: cascadeRunningRoot ? Loader2 : Play,
|
||||
classes: cascadeRunningRoot ? 'animate-spin' : undefined
|
||||
}}
|
||||
onclick={runWholePipeline}
|
||||
disabled={!!cascadeRunningRoot}
|
||||
title={cascadeRunningRoot
|
||||
? 'A pipeline run is already in progress'
|
||||
: `Run all ${allPipelineScripts.length} script${
|
||||
allPipelineScripts.length === 1 ? '' : 's'
|
||||
} in dependency order (roots first, cascading downstream)`}
|
||||
>
|
||||
{cascadeRunningRoot ? 'Running…' : 'Run pipeline'}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if mode === 'edit' && saveErrors.size > 0}
|
||||
<!-- Compact errors popover anchored next to Save all so users
|
||||
can see exactly which drafts failed and why without losing
|
||||
the editor context. Drafts that succeed disappear from
|
||||
the map; the ones still listed here are the unresolved
|
||||
failures. -->
|
||||
<Popover placement="bottom-end" contentClasses="p-3 max-w-[480px]" usePointerDownOutside>
|
||||
{#snippet trigger()}
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-1.5 px-2 py-1 rounded-md text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/30 hover:bg-red-100 dark:hover:bg-red-900/50 transition-colors text-xs font-medium"
|
||||
title="View save errors"
|
||||
>
|
||||
<AlertTriangle size={14} />
|
||||
<span>{saveErrors.size} failed</span>
|
||||
</button>
|
||||
{/snippet}
|
||||
{#snippet content()}
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-xs font-semibold text-emphasis">Save errors</span>
|
||||
<div class="flex flex-col gap-2 max-h-72 overflow-y-auto">
|
||||
{#each [...saveErrors.entries()] as [path, message]}
|
||||
<div class="flex flex-col gap-0.5 border-l-2 border-red-400 pl-2">
|
||||
<span class="text-2xs font-mono text-emphasis">{path}</span>
|
||||
<span class="text-2xs text-red-600 dark:text-red-400 break-words">
|
||||
{message}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
<Popover placement="bottom-end" contentClasses="p-3 max-w-[480px]" usePointerDownOutside>
|
||||
{#snippet trigger()}
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-1.5 px-2 py-1 rounded-md text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/30 hover:bg-red-100 dark:hover:bg-red-900/50 transition-colors text-xs font-medium"
|
||||
title="View save errors"
|
||||
>
|
||||
<AlertTriangle size={14} />
|
||||
<span>{saveErrors.size} failed</span>
|
||||
</button>
|
||||
{/snippet}
|
||||
{#snippet content()}
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-xs font-semibold text-emphasis">Save errors</span>
|
||||
<div class="flex flex-col gap-2 max-h-72 overflow-y-auto">
|
||||
{#each [...saveErrors.entries()] as [path, message]}
|
||||
<div class="flex flex-col gap-0.5 border-l-2 border-red-400 pl-2">
|
||||
<span class="text-2xs font-mono text-emphasis">{path}</span>
|
||||
<span class="text-2xs text-red-600 dark:text-red-400 break-words">
|
||||
{message}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if mode === 'edit' && pe.drafts.size > 0}
|
||||
<!-- Draft autosave status for the whole pipeline bundle. Distinct
|
||||
{/snippet}
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if mode === 'edit' && pe.drafts.size > 0}
|
||||
<!-- Draft autosave status for the whole pipeline bundle. Distinct
|
||||
from "Save all", which DEPLOYS the drafts — this only reflects
|
||||
that in-flight edits are persisted to the per-user server draft. -->
|
||||
{#if $workspaceStore}
|
||||
<AutosaveIndicator
|
||||
workspace={$workspaceStore}
|
||||
itemKind={PIPELINE_DRAFT_KIND}
|
||||
path={pipelineDraftPath}
|
||||
draftOnly
|
||||
loadedFromDraft={pe.loadedFromDbDraft}
|
||||
/>
|
||||
{#if $workspaceStore}
|
||||
<AutosaveIndicator
|
||||
workspace={$workspaceStore}
|
||||
itemKind={PIPELINE_DRAFT_KIND}
|
||||
path={pipelineDraftPath}
|
||||
draftOnly
|
||||
loadedFromDraft={pe.loadedFromDbDraft}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
variant="accent"
|
||||
unifiedSize="sm"
|
||||
startIcon={{ icon: savingAll ? Loader2 : Save }}
|
||||
onclick={saveAllDrafts}
|
||||
disabled={savingAll}
|
||||
title={savingAll ? 'Saving drafts…' : `Deploy all ${pe.drafts.size} drafts`}
|
||||
>
|
||||
{savingAll ? 'Saving…' : `Save all (${pe.drafts.size})`}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if mode === 'view'}
|
||||
<Button
|
||||
variant={activityShowing ? 'accent-secondary' : 'subtle'}
|
||||
unifiedSize="sm"
|
||||
startIcon={{ icon: History }}
|
||||
onclick={toggleActivity}
|
||||
title={activityShowing
|
||||
? 'Hide the activity panel'
|
||||
: 'Show the pipeline activity feed (recent and live runs)'}
|
||||
>
|
||||
Activity
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
variant="accent"
|
||||
variant="subtle"
|
||||
unifiedSize="sm"
|
||||
startIcon={{ icon: savingAll ? Loader2 : Save }}
|
||||
onclick={saveAllDrafts}
|
||||
disabled={savingAll}
|
||||
title={savingAll ? 'Saving drafts…' : `Deploy all ${pe.drafts.size} drafts`}
|
||||
>
|
||||
{savingAll ? 'Saving…' : `Save all (${pe.drafts.size})`}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if mode === 'view'}
|
||||
<Button
|
||||
variant={activityShowing ? 'accent-secondary' : 'subtle'}
|
||||
unifiedSize="sm"
|
||||
startIcon={{ icon: History }}
|
||||
onclick={toggleActivity}
|
||||
title={activityShowing
|
||||
? 'Hide the activity panel'
|
||||
: 'Show the pipeline activity feed (recent and live runs)'}
|
||||
>
|
||||
Activity
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
variant="subtle"
|
||||
unifiedSize="sm"
|
||||
startIcon={{ icon: RefreshCw }}
|
||||
onclick={() => graphRes.refetch()}
|
||||
disabled={graphRes.loading}
|
||||
iconOnly
|
||||
title="Refresh"
|
||||
/>
|
||||
<DropdownV2 size="sm" items={overflowMenuItems} />
|
||||
startIcon={{ icon: RefreshCw }}
|
||||
onclick={() => graphRes.refetch()}
|
||||
disabled={graphRes.loading}
|
||||
iconOnly
|
||||
title="Refresh"
|
||||
/>
|
||||
<DropdownV2 size="sm" items={overflowMenuItems} />
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<div class="flex flex-col h-full">
|
||||
|
||||
Reference in New Issue
Block a user