mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-27 16:02:43 +00:00
* feat: add a navigation picker after the breadcrumb's last segment Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: open the navigation picker among the current page's siblings Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * style: size the navigation chevron like the workspace chevron Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * style: keep one-line picker lists at one-line row height Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: show the navigation chevron only once the item's path is known Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: highlight a draft in the navigation picker by its storage path Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: key the picker's current item on the header path outside editors Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: count navigation picker picks in feature_usage Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: shrink the page header in stages and let the bar fold page actions Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: fold the runs header through the bar and wrap its filter to a second row Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: truncate the run id and fold run actions into one menu on a narrow bar Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: keep an item's summary and drop its path on a narrow page header Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: show the full summary and path read-only when they cannot be edited Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: keep secondary detail actions in menus and the star beside the summary Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: show secondary detail actions as buttons while the bar has room for them Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 346bd5417537b76591465e63b9d15bd081e9228c This commit updates the EE repository reference after PR #868 was merged in windmill-ee-private. Previous ee-repo-ref: 581b18cac39a4bee2671fd1aa86f91b9e702e8f5 New ee-repo-ref: 346bd5417537b76591465e63b9d15bd081e9228c Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
123 lines
4.5 KiB
Svelte
123 lines
4.5 KiB
Svelte
<!--
|
|
@component
|
|
The chevron after the breadcrumb's last segment: it opens the same picker a session's preview tab
|
|
uses — the workspace pages, then the folders with their flows, scripts and apps — and picking a
|
|
row navigates the app there.
|
|
|
|
It lands among the current page's siblings: an item opens in its folder with the item
|
|
highlighted, a workspace page inside Pages with that page highlighted.
|
|
-->
|
|
<script lang="ts">
|
|
import { ChevronDown } from 'lucide-svelte'
|
|
import { goto } from '$app/navigation'
|
|
import { page } from '$app/state'
|
|
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
|
import PreviewRouterPicker, { type Scope } from './sessions/PreviewRouterPicker.svelte'
|
|
import { pageKey, parsePreviewItemRoute, type PreviewTarget } from './sessions/previewRouter'
|
|
import { editPathFor, leafKeyFor, viewPathFor, type WorkspaceItem } from './workspacePicker'
|
|
import { logFeatureUsage } from '$lib/utils/featureUsage'
|
|
import type { PageHeaderItem } from './pageHeaderRegistry.svelte'
|
|
|
|
let {
|
|
item,
|
|
pagePath,
|
|
actingWorkspaceId,
|
|
iconSize = 14
|
|
}: {
|
|
/** The item the page is about, to land in its folder. */
|
|
item?: PageHeaderItem
|
|
/** The route of a workspace page, to highlight it among the pages. */
|
|
pagePath?: string
|
|
/** Load from, and navigate within, this workspace rather than the one the app points at. */
|
|
actingWorkspaceId?: string
|
|
iconSize?: number
|
|
} = $props()
|
|
|
|
let isOpen = $state(false)
|
|
|
|
// Two paths for one item, which differ on a draft: the header names it by the path it will
|
|
// deploy to, which is the folder the tree files it under, while its row is keyed by where it
|
|
// is stored, which is the path in an editor's URL. A viewer's URL is no help: a script page
|
|
// can be addressed by a version hash, so there the header's path is the row's.
|
|
const route = $derived(item?.path ? parsePreviewItemRoute(page.url.pathname) : null)
|
|
const currentItem = $derived.by<WorkspaceItem | undefined>(() => {
|
|
const kind = route?.kind ?? item?.kind
|
|
const path = route?.mode === 'edit' ? route.itemPath : item?.path
|
|
if (!kind || !path) return undefined
|
|
return { path, kind, raw_app: route?.raw_app, summary: item?.summary ?? '' }
|
|
})
|
|
|
|
// The item's own folder is its path minus the name.
|
|
const initialScope = $derived.by<Scope>(() => {
|
|
const parts = item?.path?.split('/') ?? []
|
|
return parts.length >= 3 ? { kind: 'all', dir: parts.slice(0, -1).join('/') } : undefined
|
|
})
|
|
const initialHighlight = $derived(
|
|
currentItem
|
|
? leafKeyFor(currentItem.kind, currentItem.path)
|
|
: pagePath
|
|
? pageKey(pagePath)
|
|
: undefined
|
|
)
|
|
|
|
// A page acting on another workspace than the app's (a session in a fork) keeps acting on it:
|
|
// the route reads `?workspace=` before the store.
|
|
function withWorkspace(href: string): string {
|
|
if (!actingWorkspaceId) return href
|
|
const sep = href.includes('?') ? '&' : '?'
|
|
return `${href}${sep}workspace=${encodeURIComponent(actingWorkspaceId)}`
|
|
}
|
|
|
|
function hrefFor(target: PreviewTarget): string | undefined {
|
|
if (target.type === 'page') return target.href
|
|
if (target.type === 'item')
|
|
return target.mode === 'view' ? viewPathFor(target.item) : editPathFor(target.item)
|
|
return undefined
|
|
}
|
|
|
|
/** The whole vocabulary of the `header_nav` / `pick` counter: what kind of destination was
|
|
* picked, never which one. */
|
|
type PickKey = 'page' | 'item_view' | 'item_edit'
|
|
|
|
function pickKey(target: PreviewTarget): PickKey | undefined {
|
|
if (target.type === 'page') return 'page'
|
|
if (target.type === 'item') return target.mode === 'view' ? 'item_view' : 'item_edit'
|
|
return undefined
|
|
}
|
|
|
|
function pick(target: PreviewTarget) {
|
|
isOpen = false
|
|
const href = hrefFor(target)
|
|
if (!href) return
|
|
const key = pickKey(target)
|
|
if (key) logFeatureUsage('header_nav', 'pick', { key, workspace: actingWorkspaceId })
|
|
goto(withWorkspace(href))
|
|
}
|
|
</script>
|
|
|
|
<Popover
|
|
placement="bottom-start"
|
|
usePointerDownOutside
|
|
excludeSelectors=".drawer"
|
|
disableFocusTrap
|
|
closeOnOtherPopoverOpen
|
|
bind:isOpen
|
|
openFocus="[data-workspace-picker-search]"
|
|
contentClasses="flex flex-col overflow-hidden"
|
|
class="shrink-0 flex items-center h-9 px-1.5 rounded text-tertiary hover:bg-surface-hover hover:text-primary transition-colors"
|
|
triggerAttrs={{ 'aria-label': 'Navigate to', title: 'Navigate to' }}
|
|
>
|
|
{#snippet trigger()}
|
|
<ChevronDown size={iconSize} class="flex-shrink-0" />
|
|
{/snippet}
|
|
{#snippet content()}
|
|
<PreviewRouterPicker
|
|
{initialScope}
|
|
{initialHighlight}
|
|
{currentItem}
|
|
workspaceId={actingWorkspaceId}
|
|
onPick={pick}
|
|
/>
|
|
{/snippet}
|
|
</Popover>
|