mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-12 08:02:44 +00:00
fix: open the navigation picker among the current page's siblings
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
43aab55bfb
commit
3c74ce0847
@@ -4,8 +4,8 @@ The chevron after the breadcrumb's last segment: it opens the same picker a sess
|
||||
uses — the workspace pages, then the folders with their flows, scripts and apps — and picking a
|
||||
row navigates the app there.
|
||||
|
||||
It lands where the page is: an item's folder with the item highlighted, or a page highlighted at
|
||||
the root, so moving to a sibling is one step rather than a drill from the top.
|
||||
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'
|
||||
@@ -34,8 +34,7 @@ the root, so moving to a sibling is one step rather than a drill from the top.
|
||||
|
||||
let isOpen = $state(false)
|
||||
|
||||
// The tree files a path under its `f/<folder>` or `u/<user>` scope, then one branch per
|
||||
// subfolder, each keyed by its full path — so the item's own folder is its path minus the name.
|
||||
// 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
|
||||
|
||||
@@ -43,6 +43,7 @@ section or the flat layout.
|
||||
PREVIEW_PAGES,
|
||||
artifactKey,
|
||||
isArtifactKey,
|
||||
isPageKey,
|
||||
pageHref,
|
||||
pageKey,
|
||||
previewModeFor,
|
||||
@@ -243,16 +244,18 @@ section or the flat layout.
|
||||
)
|
||||
])
|
||||
|
||||
// An artifact highlight lives under the 'artifacts' branch, which the legacy
|
||||
// {kind, dir} scope can't express — open the picker inside that branch so the
|
||||
// active artifact is actually visible and highlighted (not the first root row).
|
||||
// Keyed on the highlight's shape alone: the branch itself may not have
|
||||
// materialized yet (artifacts hydrate from IndexedDB after tabs restore), and
|
||||
// the drill entries fill in reactively once it does.
|
||||
// A page or artifact highlight lives under its own branch, which the legacy
|
||||
// {kind, dir} scope can't express — open the picker inside that branch, so the
|
||||
// reader standing on one sees its siblings with it highlighted. Keyed on the
|
||||
// highlight's shape alone: the artifacts branch may not have materialized yet
|
||||
// (artifacts hydrate from IndexedDB after tabs restore), and the drill entries
|
||||
// fill in reactively once it does.
|
||||
const computedInitialScope = untrack(() =>
|
||||
initialHighlight && isArtifactKey(initialHighlight)
|
||||
? ['artifacts']
|
||||
: legacyScopeToPath(initialScope, kinds, 'flat')
|
||||
: initialHighlight && isPageKey(initialHighlight)
|
||||
? ['pages']
|
||||
: legacyScopeToPath(initialScope, kinds, 'flat')
|
||||
)
|
||||
|
||||
// The flat root has no branch to carry a per-kind loading flag — surface
|
||||
|
||||
@@ -141,6 +141,7 @@ export function triggerLabelForPath(path: string): string | undefined {
|
||||
}
|
||||
|
||||
export const pageKey = (path: string) => `page:${path}`
|
||||
export const isPageKey = (key: string) => key.startsWith('page:')
|
||||
export const pageHref = (path: string) => `${base}${path}`
|
||||
|
||||
/** Strip the deployment base prefix (and any query/hash) from a preview path
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
ASSETS_PATH,
|
||||
AUDIT_LOGS_PATH,
|
||||
isPageKey,
|
||||
FOLDERS_PATH,
|
||||
GROUPS_PATH,
|
||||
isPageItemListPath,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
// Re-exported so the preview code that already reads locations through this module keeps
|
||||
// one import, while a caller needing only a path can reach for the leaf instead.
|
||||
export {
|
||||
isPageKey,
|
||||
pageItemListPath,
|
||||
pageItemUrl,
|
||||
pageKey,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type WorkspaceItem,
|
||||
type WorkspaceItemKind
|
||||
} from './workspacePicker'
|
||||
import type { DrillBranch, DrillLeaf, DrillNode } from './drillPicker'
|
||||
import { resolveScope, type DrillBranch, type DrillLeaf, type DrillNode } from './drillPicker'
|
||||
|
||||
const item = (
|
||||
kind: WorkspaceItemKind,
|
||||
@@ -522,6 +522,17 @@ describe('legacyScopeToPath', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('walks every ancestor folder, so a nested dir resolves in the built tree', () => {
|
||||
const loaded = { flow: [item('flow', 'f/a/b/c')], script: [] }
|
||||
for (const layout of ['flat', 'by-kind'] as const) {
|
||||
const tree = buildWorkspaceTree({ loaded, kinds: ['flow', 'script'], loadingKind: {}, layout })
|
||||
const path = legacyScopeToPath({ kind: 'flow', dir: 'f/a/b' }, ['flow', 'script'], layout)
|
||||
expect(resolveScope(tree, path)?.children.map((c) => c.key)).toEqual([
|
||||
leafKeyFor('flow', 'f/a/b/c')
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
it('flat: returns [] without a dir', () => {
|
||||
expect(legacyScopeToPath({ kind: 'all' }, ['flow', 'script'], 'flat')).toEqual([])
|
||||
})
|
||||
|
||||
@@ -271,19 +271,20 @@ export function legacyScopeToPath(
|
||||
layout: 'by-kind' | 'flat' = 'by-kind'
|
||||
): string[] {
|
||||
if (!scope) return []
|
||||
// A subfolder is a child of its parent's branch, not a root entry, so a scope into
|
||||
// `f/a/b` must walk `f/a` first or it resolves to nothing.
|
||||
const dirChain = (kind: WorkspaceItemKind | 'all') => {
|
||||
if (!scope.dir) return []
|
||||
const parts = scope.dir.split('/')
|
||||
return parts.slice(1).map((_, i) => dirKey(kind, parts.slice(0, i + 2).join('/')))
|
||||
}
|
||||
// Flat layout: no kind branches at all — scope dirs live at root and are
|
||||
// always keyed under the cross-kind 'all' namespace.
|
||||
if (layout === 'flat') {
|
||||
return scope.dir ? [dirKey('all', scope.dir)] : []
|
||||
}
|
||||
if (layout === 'flat') return dirChain('all')
|
||||
// Single-kind mode: there's no kind branch at root; scope's `kind` is
|
||||
// implicit. Only the dir (if any) makes it to the path.
|
||||
if (kinds.length === 1) {
|
||||
return scope.dir ? [dirKey(scope.kind, scope.dir)] : []
|
||||
}
|
||||
const path: string[] = [kindKey(scope.kind)]
|
||||
if (scope.dir) path.push(dirKey(scope.kind, scope.dir))
|
||||
return path
|
||||
if (kinds.length === 1) return dirChain(scope.kind)
|
||||
return [kindKey(scope.kind), ...dirChain(scope.kind)]
|
||||
}
|
||||
|
||||
/** Return `absolutePath` shortened to its segment relative to the deepest
|
||||
|
||||
Reference in New Issue
Block a user