mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 16:05:58 +00:00
* feat: open an AI session from the runs and trigger pages * feat: tell the chat which page the session preview shows * fix: observe shallow url writes and keep page tabs deduped by path * feat: open an AI session from the resource and variable drawers * fix: re-point page tabs on hash change and follow the drawer's workspace * fix: force a load when a page tab is re-pointed within one document * fix: report a re-pointed preview tab as retargeted, not opened * fix: reload a preview tab re-pointed at the url the frame drifted from * fix: canonicalize runs previews and read drawer anchors per page * fix: dedupe page tabs on the path so self-written filters don't duplicate * perf: carry the active-preview rule only in chats that have a side panel * fix: read a preview tab's hash as a row only where the page deep-links one * fix: focus the preview tab showing the exact location before retargeting by path * refactor: give preview locations one module that says what they mean * fix: report the active preview from what is on screen, not the selected tab * fix: read a preview location's view from the params a request can set * fix: count every filter a request can set, and flush drawer drafts before routing * docs: state each preview-routing constraint once, within four lines * fix: take a page's view params from the filter schema it already declares * docs: describe the filter contract the url builders now follow * fix: describe a preview to the model from addressing fields only * fix: keep a filter value holding a delimiter apart from two filters * fix: keep a preview description to one line the model can trust * fix: materialize the resource editors before persisting the draft * docs: bring the preview-routing constraints back within four lines * fix: refuse to route a preview on state the drawer could not persist * fix: read a resource drawer's validity from the editor, not from draft dirtiness * fix: answer what the user can see from one place in both descriptions * refactor: name each write to a preview tab's two locations, and the read * fix: flush only editors holding a pending change * fix: drop a list page's row anchor when its drawer closes * fix: clear the row anchor on every list page that deep-links one * fix: keep a closed drawer closed, and refuse to leave unparseable text * refactor: register the resource json field in the shared unparseable set * refactor: decide a forced load where the command changes, not in the host * fix: navigate a preview frame only when it is not already there * fix: boot a remounted preview frame where the user left it * fix: carry a list page's filters and open row into the session Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: navigate a preview frame by what it shows, not by its url Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: read a resource's raw-editor validity from the current parse Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: compare preview views without iterating URLSearchParams Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: drop re-exports the path leaf left without readers Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
179 lines
5.6 KiB
Svelte
179 lines
5.6 KiB
Svelte
<script lang="ts">
|
|
import { Button, Drawer } from './common'
|
|
|
|
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
|
|
|
import { History, Loader2, Save } from 'lucide-svelte'
|
|
import WsSpecificVersions from './WsSpecificVersions.svelte'
|
|
import { userStore, workspaceStore } from '$lib/stores'
|
|
import { isOwner } from '$lib/utils'
|
|
import LocalDraftBanner from './LocalDraftBanner.svelte'
|
|
import OpenInSessionButton from './sessions/OpenInSessionButton.svelte'
|
|
import {
|
|
clearPageDrawerAnchor,
|
|
pageDrawerSessionSource,
|
|
setPageDrawerAnchor
|
|
} from './sessions/pageDrawerSession'
|
|
import { RESOURCES_PATH } from './sessions/previewPaths'
|
|
import ResourceVersionHistory from './ResourceVersionHistory.svelte'
|
|
|
|
let {
|
|
workspace = undefined,
|
|
disableChatOffset = false,
|
|
onRestored = undefined
|
|
}: { workspace?: string; disableChatOffset?: boolean; onRestored?: () => void } = $props()
|
|
|
|
let drawer: Drawer | undefined = $state()
|
|
let historyDrawer: Drawer | undefined = $state()
|
|
let canSave = $state(true)
|
|
let resource_type: string | undefined = $state(undefined)
|
|
let defaultValues: Record<string, any> | undefined = $state(undefined)
|
|
|
|
let resourceEditor:
|
|
| {
|
|
save: () => void
|
|
localDraftDeployed: () => unknown
|
|
localDraftCurrent: () => unknown
|
|
discardLocalDraft: () => void
|
|
}
|
|
| undefined = $state(undefined)
|
|
let hasLocalDraft = $state(false)
|
|
let canWriteSelected = $state(true)
|
|
|
|
let path: string | undefined = $state(undefined)
|
|
let selected: string | undefined = $state(undefined)
|
|
|
|
let effectiveWorkspace = $derived(workspace ?? $workspaceStore!)
|
|
// The editor renders whichever workspace-specific variant `selected` points at, so history has
|
|
// to follow it too — otherwise a restore would write over the variant the user is not looking at.
|
|
let historyWorkspace = $derived(selected ?? effectiveWorkspace)
|
|
// Clearing is irreversible and the backend gates it on ownership, not write access. $userStore
|
|
// describes the user in the workspace they are signed into, so it can only answer for that one:
|
|
// history pointed anywhere else — a ws-specific variant, or an explicit `workspace` prop — gets
|
|
// no Clear button rather than a verdict computed from the wrong membership.
|
|
let canClearSelected = $derived(
|
|
historyWorkspace === $workspaceStore && isOwner(path ?? '', $userStore, $workspaceStore)
|
|
)
|
|
|
|
export async function initEdit(p: string): Promise<void> {
|
|
resource_type = undefined
|
|
path = p
|
|
selected = effectiveWorkspace
|
|
drawer?.openDrawer?.()
|
|
setPageDrawerAnchor(RESOURCES_PATH, p)
|
|
}
|
|
|
|
export async function initNew(
|
|
resourceType: string,
|
|
nDefaultValues?: Record<string, any>
|
|
): Promise<void> {
|
|
path = undefined
|
|
resource_type = resourceType
|
|
defaultValues = nDefaultValues
|
|
selected = effectiveWorkspace
|
|
drawer?.openDrawer?.()
|
|
}
|
|
|
|
let mode: 'edit' | 'new' = $derived(!path ? 'new' : 'edit')
|
|
|
|
// `selected`, not `effectiveWorkspace`: WsSpecificVersions re-points this drawer
|
|
// at another workspace's version, and the session must act on the one shown.
|
|
const sessionSource = $derived(
|
|
pageDrawerSessionSource(RESOURCES_PATH, path, selected ?? effectiveWorkspace)
|
|
)
|
|
</script>
|
|
|
|
<Drawer
|
|
bind:this={drawer}
|
|
size="50rem"
|
|
{disableChatOffset}
|
|
on:close={() => clearPageDrawerAnchor(RESOURCES_PATH)}
|
|
>
|
|
<DrawerContent
|
|
title={mode == 'edit' ? 'Edit ' + path : 'Add a resource'}
|
|
bannerReserved={mode == 'edit'}
|
|
on:close={drawer?.closeDrawer}
|
|
>
|
|
{#await import('./ResourceEditor.svelte')}
|
|
<Loader2 class="animate-spin" />
|
|
{:then Module}
|
|
<Module.default
|
|
{path}
|
|
{resource_type}
|
|
{defaultValues}
|
|
{workspace}
|
|
on:refresh
|
|
bind:this={resourceEditor}
|
|
bind:canSave
|
|
bind:selected
|
|
onDraftStateChange={(v) => (hasLocalDraft = v)}
|
|
onCanWriteChange={(v) => (canWriteSelected = v)}
|
|
/>
|
|
{/await}
|
|
{#snippet banner()}
|
|
<LocalDraftBanner
|
|
show={hasLocalDraft}
|
|
reserveSpace={mode == 'edit'}
|
|
getDeployed={() => resourceEditor?.localDraftDeployed()}
|
|
getCurrent={() => resourceEditor?.localDraftCurrent()}
|
|
onDiscard={() => resourceEditor?.discardLocalDraft()}
|
|
disabled={!canWriteSelected}
|
|
/>
|
|
{/snippet}
|
|
{#snippet actions()}
|
|
<OpenInSessionButton source={sessionSource} />
|
|
{#if mode == 'edit' && path && effectiveWorkspace}
|
|
<Button
|
|
variant="default"
|
|
unifiedSize="md"
|
|
startIcon={{ icon: History }}
|
|
on:click={() => historyDrawer?.openDrawer()}
|
|
>
|
|
History
|
|
</Button>
|
|
<WsSpecificVersions
|
|
kind="resource"
|
|
workspaceId={effectiveWorkspace}
|
|
initialPath={path}
|
|
bind:selected
|
|
/>
|
|
{/if}
|
|
<Button
|
|
variant="accent"
|
|
unifiedSize="md"
|
|
startIcon={{ icon: Save }}
|
|
on:click={() => {
|
|
resourceEditor?.save()
|
|
drawer?.closeDrawer()
|
|
}}
|
|
disabled={!canSave}
|
|
>
|
|
Save
|
|
</Button>
|
|
{/snippet}
|
|
</DrawerContent>
|
|
</Drawer>
|
|
|
|
<Drawer bind:this={historyDrawer} size="1200px">
|
|
<DrawerContent title="Versions History" on:close={historyDrawer?.closeDrawer} noPadding>
|
|
{#if path && historyWorkspace}
|
|
<ResourceVersionHistory
|
|
{path}
|
|
workspace={historyWorkspace}
|
|
canRestore={canWriteSelected}
|
|
canClear={canClearSelected}
|
|
onRestore={() => {
|
|
historyDrawer?.closeDrawer()
|
|
// Close the editor too. It holds a baseline captured before the restore, and
|
|
// any local draft on top of it, so saving from it afterwards would write the
|
|
// pre-restore value straight back over the version just restored.
|
|
drawer?.closeDrawer()
|
|
// Its own callback rather than the `refresh` event: callers bind that to
|
|
// reopening a picker (EditorBar), which a restore should not trigger.
|
|
onRestored?.()
|
|
}}
|
|
/>
|
|
{/if}
|
|
</DrawerContent>
|
|
</Drawer>
|