mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-18 16:03:24 +00:00
feat(sessions): offer the way back to the list from an entity editor tab
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
This commit is contained in:
co-authored by
Claude Opus 5
parent
4d9ec4b816
commit
1b840dbd7f
@@ -3,7 +3,7 @@
|
||||
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
|
||||
import { History, Loader2, Save } from 'lucide-svelte'
|
||||
import { ArrowLeft, History, Loader2, Save } from 'lucide-svelte'
|
||||
import WsSpecificVersions from './WsSpecificVersions.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { isOwner } from '$lib/utils'
|
||||
@@ -24,7 +24,8 @@
|
||||
disableChatOffset = false,
|
||||
onRestored = undefined,
|
||||
onSaved = undefined,
|
||||
useDrawer = true
|
||||
useDrawer = true,
|
||||
onBack = undefined
|
||||
}: {
|
||||
workspace?: string
|
||||
disableChatOffset?: boolean
|
||||
@@ -39,6 +40,9 @@
|
||||
* drawer to open, so it simply takes effect.
|
||||
*/
|
||||
useDrawer?: boolean
|
||||
/** Inline only: offered in the header when the host replaced something the
|
||||
* user should be able to get back to (a session tab that took over the list). */
|
||||
onBack?: () => void
|
||||
} = $props()
|
||||
|
||||
let drawer: Drawer | undefined = $state()
|
||||
@@ -233,9 +237,21 @@
|
||||
{:else}
|
||||
<div class="flex flex-col h-full min-h-0">
|
||||
<div class="flex flex-row items-center gap-2 justify-between px-4 py-2 border-b">
|
||||
<span class="text-sm font-semibold truncate"
|
||||
>{mode == 'edit' ? 'Edit ' + path : addResourceTitle(resource_type)}</span
|
||||
>
|
||||
<div class="flex flex-row items-center gap-2 min-w-0">
|
||||
{#if onBack}
|
||||
<Button
|
||||
variant="subtle"
|
||||
unifiedSize="sm"
|
||||
startIcon={{ icon: ArrowLeft }}
|
||||
on:click={onBack}
|
||||
title="Back to Resources"
|
||||
iconOnly
|
||||
/>
|
||||
{/if}
|
||||
<span class="text-sm font-semibold truncate"
|
||||
>{mode == 'edit' ? 'Edit ' + path : addResourceTitle(resource_type)}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2 shrink-0">
|
||||
{@render editorActions()}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { canWrite } from '$lib/utils'
|
||||
import { Save } from 'lucide-svelte'
|
||||
import { ArrowLeft, Save } from 'lucide-svelte'
|
||||
import VariableForm from './VariableForm.svelte'
|
||||
import { invalidateWorkspacePaths } from './PathNameAutocomplete.svelte'
|
||||
import WsSpecificVersions from './WsSpecificVersions.svelte'
|
||||
@@ -41,7 +41,8 @@
|
||||
// rather than in the navigation workspace. Defaults to $workspaceStore.
|
||||
let {
|
||||
workspace = undefined,
|
||||
useDrawer = true
|
||||
useDrawer = true,
|
||||
onBack = undefined
|
||||
}: {
|
||||
workspace?: string
|
||||
/**
|
||||
@@ -51,6 +52,9 @@
|
||||
* no drawer to open, so it simply takes effect.
|
||||
*/
|
||||
useDrawer?: boolean
|
||||
/** Inline only: offered in the header when the host replaced something the
|
||||
* user should be able to get back to (a session tab that took over the list). */
|
||||
onBack?: () => void
|
||||
} = $props()
|
||||
let curWs = $derived(workspace ?? $workspaceStore)
|
||||
|
||||
@@ -409,9 +413,21 @@
|
||||
{:else}
|
||||
<div class="flex flex-col h-full min-h-0">
|
||||
<div class="flex flex-row items-center gap-2 justify-between px-4 py-2 border-b">
|
||||
<span class="text-sm font-semibold truncate"
|
||||
>{edit ? `Update variable at ${initialPath}` : 'Add a variable'}</span
|
||||
>
|
||||
<div class="flex flex-row items-center gap-2 min-w-0">
|
||||
{#if onBack}
|
||||
<Button
|
||||
variant="subtle"
|
||||
unifiedSize="sm"
|
||||
startIcon={{ icon: ArrowLeft }}
|
||||
on:click={onBack}
|
||||
title="Back to Variables"
|
||||
iconOnly
|
||||
/>
|
||||
{/if}
|
||||
<span class="text-sm font-semibold truncate"
|
||||
>{edit ? `Update variable at ${initialPath}` : 'Add a variable'}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2 shrink-0">
|
||||
{@render editorActions()}
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
import type { SessionRuntime } from './sessionRuntime.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import {
|
||||
entityListPage,
|
||||
pageHref,
|
||||
resolvePreviewTab,
|
||||
parsePreviewItemRoute,
|
||||
parsePreviewSelectedId,
|
||||
@@ -142,6 +144,20 @@
|
||||
active ? 'z-10 opacity-100 pointer-events-auto' : 'z-0 opacity-0 pointer-events-none'
|
||||
)
|
||||
|
||||
// An entity editor replaced the list its tab was opened from (the row is the
|
||||
// tab now, not a drawer over the list), so it has to offer the way back.
|
||||
// Re-points this tab rather than opening another: the list is where the tab
|
||||
// came from, not a second destination.
|
||||
const backToList = $derived(
|
||||
slot.kind === 'entity' && runtime
|
||||
? () => {
|
||||
const page = entityListPage(slot.entityKind)
|
||||
if (page)
|
||||
runtime.previewTabs.navigate({ type: 'page', href: pageHref(page.path), label: page.label })
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
|
||||
// Overlays a tab opens (drawers, modals, popovers) anchor here rather than to the
|
||||
// document, so they stay within this tab and hide with it when another tab takes over.
|
||||
// Every branch that renders content in-realm must bind this — an unbound host makes the
|
||||
@@ -324,19 +340,19 @@
|
||||
{#await import('./ScheduleEditorView.svelte')}
|
||||
{@render editorLoading()}
|
||||
{:then Module}
|
||||
<Module.default path={slot.path} {workspaceId} />
|
||||
<Module.default path={slot.path} {workspaceId} onBack={backToList} />
|
||||
{/await}
|
||||
{:else if slot.entityKind === 'resource'}
|
||||
{#await import('./ResourceEditorView.svelte')}
|
||||
{@render editorLoading()}
|
||||
{:then Module}
|
||||
<Module.default path={slot.path} {workspaceId} />
|
||||
<Module.default path={slot.path} {workspaceId} onBack={backToList} />
|
||||
{/await}
|
||||
{:else}
|
||||
{#await import('./VariableEditorView.svelte')}
|
||||
{@render editorLoading()}
|
||||
{:then Module}
|
||||
<Module.default path={slot.path} {workspaceId} />
|
||||
<Module.default path={slot.path} {workspaceId} onBack={backToList} />
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
|
||||
let {
|
||||
path,
|
||||
workspaceId
|
||||
workspaceId,
|
||||
onBack
|
||||
}: {
|
||||
/** The resource this tab edits (the row its location deep-links). */
|
||||
path: string
|
||||
/** The session's acting workspace, which the editor operates on instead of
|
||||
* `$workspaceStore` (the nav workspace, which a session leaves put). */
|
||||
workspaceId: string
|
||||
/** Back to the list this editor was reached through; the tab replaced it. */
|
||||
onBack?: () => void
|
||||
} = $props()
|
||||
|
||||
let editor = $state<ResourceEditorDrawer | undefined>()
|
||||
@@ -25,4 +28,4 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<ResourceEditorDrawer bind:this={editor} useDrawer={false} workspace={workspaceId} />
|
||||
<ResourceEditorDrawer bind:this={editor} useDrawer={false} workspace={workspaceId} {onBack} />
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
<script lang="ts">
|
||||
import ScheduleEditorInner from '$lib/components/triggers/schedules/ScheduleEditorInner.svelte'
|
||||
import { setTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { ArrowLeft } from 'lucide-svelte'
|
||||
import { untrack } from 'svelte'
|
||||
|
||||
let {
|
||||
path,
|
||||
workspaceId
|
||||
workspaceId,
|
||||
onBack
|
||||
}: {
|
||||
/** The schedule this tab edits (the row its location deep-links). */
|
||||
path: string
|
||||
/** The session's acting workspace, which the whole trigger subtree reads
|
||||
* through the `triggerWorkspace` resolver instead of `$workspaceStore`. */
|
||||
workspaceId: string
|
||||
/** Back to the list this editor was reached through; the tab replaced it. */
|
||||
onBack?: () => void
|
||||
} = $props()
|
||||
|
||||
// Captured at init, so it must read the current prop rather than close over it.
|
||||
@@ -33,6 +38,23 @@
|
||||
|
||||
<div class="h-full w-full overflow-auto p-4">
|
||||
<!-- useDrawer=false renders the editor as a Section, the same inline form the
|
||||
script/flow trigger panel mounts (see SchedulePanel). -->
|
||||
<ScheduleEditorInner bind:this={editor} useDrawer={false} allowDraft showDraftBanner />
|
||||
script/flow trigger panel mounts (see SchedulePanel). `customLabel` is that
|
||||
Section's header, which is where the way back belongs. -->
|
||||
<ScheduleEditorInner bind:this={editor} useDrawer={false} allowDraft showDraftBanner>
|
||||
{#snippet customLabel()}
|
||||
<div class="flex flex-row items-center gap-2 min-w-0">
|
||||
{#if onBack}
|
||||
<Button
|
||||
variant="subtle"
|
||||
unifiedSize="sm"
|
||||
startIcon={{ icon: ArrowLeft }}
|
||||
on:click={onBack}
|
||||
title="Back to Schedules"
|
||||
iconOnly
|
||||
/>
|
||||
{/if}
|
||||
<span class="text-sm font-semibold truncate">Schedule</span>
|
||||
</div>
|
||||
{/snippet}
|
||||
</ScheduleEditorInner>
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
|
||||
let {
|
||||
path,
|
||||
workspaceId
|
||||
workspaceId,
|
||||
onBack
|
||||
}: {
|
||||
/** The variable this tab edits (the row its location deep-links). */
|
||||
path: string
|
||||
/** The session's acting workspace, which the editor operates on instead of
|
||||
* `$workspaceStore` (the nav workspace, which a session leaves put). */
|
||||
workspaceId: string
|
||||
/** Back to the list this editor was reached through; the tab replaced it. */
|
||||
onBack?: () => void
|
||||
} = $props()
|
||||
|
||||
let editor = $state<VariableEditor | undefined>()
|
||||
@@ -25,4 +28,4 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<VariableEditor bind:this={editor} useDrawer={false} workspace={workspaceId} />
|
||||
<VariableEditor bind:this={editor} useDrawer={false} workspace={workspaceId} {onBack} />
|
||||
|
||||
@@ -133,6 +133,14 @@ const IN_REALM_ENTITY_PAGES: Partial<Record<string, EntityEditorKind>> = {
|
||||
[VARIABLES_PATH]: 'variable'
|
||||
}
|
||||
|
||||
/** The list page an entity editor was reached through, so its host can offer the
|
||||
* way back. Undefined only if a kind is registered above without a curated page,
|
||||
* which `PREVIEW_PAGES` covers for all three today. */
|
||||
export function entityListPage(kind: EntityEditorKind): PreviewPage | undefined {
|
||||
const path = Object.keys(IN_REALM_ENTITY_PAGES).find((p) => IN_REALM_ENTITY_PAGES[p] === kind)
|
||||
return path ? matchPreviewPage(path) : undefined
|
||||
}
|
||||
|
||||
/** The workspace entity a preview location opens the editor of — a list page
|
||||
* with a row deep-linked in its hash, for the pages whose editor mounts in
|
||||
* process. Undefined for the bare list page (an iframe of the list) and for
|
||||
|
||||
Reference in New Issue
Block a user