feat(ui): move presets to their own top nav entry at /presets
Drop the Items | Presets switch from the catalog header, give presets a dedicated header and redirect the old /catalog/presets routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
30ddc7a parent: 9e4bfeb modified
packages/ui/app/components/AppHeader.vue +2 -1 | @@ -7,7 +7,7 @@ | ||
| 7 | 7 | const route = useRoute() |
| 8 | 8 | const { projects } = useProjects() |
| 9 | 9 | const { home, refresh: refreshHome } = useHomeInventory() |
| 10 | -const { items, exists, git } = useCatalog() | |
| 10 | +const { items, presets, exists, git } = useCatalog() | |
| 11 | 11 | const { busy, run } = useCatalogGit() |
| 12 | 12 | const colorMode = useColorMode() |
| 13 | 13 | const switcher = useSwitcher() |
| @@ -22,6 +22,7 @@ const nav = computed<NavItem[]>(() => [ | ||
| 22 | 22 | // Global (the Home scope) is the pinned first row of Projects. |
| 23 | 23 | { label: 'Projects', to: '/projects', count: projects.value.length }, |
| 24 | 24 | { label: 'Catalog', to: '/catalog', count: exists.value ? items.value.length : undefined }, |
| 25 | + { label: 'Presets', to: '/presets', count: exists.value ? presets.value.length : undefined }, | |
| 25 | 26 | { label: 'Marketplaces', to: '/marketplaces' }, |
| 26 | 27 | { label: 'Sync', to: '/sync' } |
| 27 | 28 | ]) |
| @@ -7,7 +7,7 @@ | |||
| 7 | const route = useRoute() | 7 | const route = useRoute() |
| 8 | const { projects } = useProjects() | 8 | const { projects } = useProjects() |
| 9 | const { home, refresh: refreshHome } = useHomeInventory() | 9 | const { home, refresh: refreshHome } = useHomeInventory() |
| 10 | -const { items, exists, git } = useCatalog() | 10 | +const { items, presets, exists, git } = useCatalog() |
| 11 | const { busy, run } = useCatalogGit() | 11 | const { busy, run } = useCatalogGit() |
| 12 | const colorMode = useColorMode() | 12 | const colorMode = useColorMode() |
| 13 | const switcher = useSwitcher() | 13 | const switcher = useSwitcher() |
| @@ -22,6 +22,7 @@ const nav = computed<NavItem[]>(() => [ | |||
| 22 | // Global (the Home scope) is the pinned first row of Projects. | 22 | // Global (the Home scope) is the pinned first row of Projects. |
| 23 | { label: 'Projects', to: '/projects', count: projects.value.length }, | 23 | { label: 'Projects', to: '/projects', count: projects.value.length }, |
| 24 | { label: 'Catalog', to: '/catalog', count: exists.value ? items.value.length : undefined }, | 24 | { label: 'Catalog', to: '/catalog', count: exists.value ? items.value.length : undefined }, |
| 25 | + { label: 'Presets', to: '/presets', count: exists.value ? presets.value.length : undefined }, | ||
| 25 | { label: 'Marketplaces', to: '/marketplaces' }, | 26 | { label: 'Marketplaces', to: '/marketplaces' }, |
| 26 | { label: 'Sync', to: '/sync' } | 27 | { label: 'Sync', to: '/sync' } |
| 27 | ]) | 28 | ]) |
modified
packages/ui/app/components/AppSwitcher.vue +2 -2 | @@ -19,7 +19,7 @@ const PAGES = [ | ||
| 19 | 19 | { label: 'Global', to: GLOBAL_PATH, description: 'Loaded in every project (~)' }, |
| 20 | 20 | { label: 'Projects', to: '/projects' }, |
| 21 | 21 | { label: 'Catalog', to: '/catalog' }, |
| 22 | - { label: 'Presets', to: '/catalog/presets' }, | |
| 22 | + { label: 'Presets', to: '/presets' }, | |
| 23 | 23 | { label: 'Marketplaces', to: '/marketplaces' }, |
| 24 | 24 | { label: 'Sync', to: '/sync' }, |
| 25 | 25 | { label: 'Settings', to: '/settings' } |
| @@ -54,7 +54,7 @@ const groups = computed(() => [ | ||
| 54 | 54 | { |
| 55 | 55 | id: 'presets', |
| 56 | 56 | label: 'Presets', |
| 57 | - items: presets.value.map(p => ({ label: p.name, suffix: 'Preset', onSelect: () => go(`/catalog/presets/${encodeURIComponent(p.name)}`) })) | |
| 57 | + items: presets.value.map(p => ({ label: p.name, suffix: 'Preset', onSelect: () => go(`/presets/${encodeURIComponent(p.name)}`) })) | |
| 58 | 58 | } |
| 59 | 59 | ] |
| 60 | 60 | : []) |
| @@ -19,7 +19,7 @@ const PAGES = [ | |||
| 19 | { label: 'Global', to: GLOBAL_PATH, description: 'Loaded in every project (~)' }, | 19 | { label: 'Global', to: GLOBAL_PATH, description: 'Loaded in every project (~)' }, |
| 20 | { label: 'Projects', to: '/projects' }, | 20 | { label: 'Projects', to: '/projects' }, |
| 21 | { label: 'Catalog', to: '/catalog' }, | 21 | { label: 'Catalog', to: '/catalog' }, |
| 22 | - { label: 'Presets', to: '/catalog/presets' }, | 22 | + { label: 'Presets', to: '/presets' }, |
| 23 | { label: 'Marketplaces', to: '/marketplaces' }, | 23 | { label: 'Marketplaces', to: '/marketplaces' }, |
| 24 | { label: 'Sync', to: '/sync' }, | 24 | { label: 'Sync', to: '/sync' }, |
| 25 | { label: 'Settings', to: '/settings' } | 25 | { label: 'Settings', to: '/settings' } |
| @@ -54,7 +54,7 @@ const groups = computed(() => [ | |||
| 54 | { | 54 | { |
| 55 | id: 'presets', | 55 | id: 'presets', |
| 56 | label: 'Presets', | 56 | label: 'Presets', |
| 57 | - items: presets.value.map(p => ({ label: p.name, suffix: 'Preset', onSelect: () => go(`/catalog/presets/${encodeURIComponent(p.name)}`) })) | 57 | + items: presets.value.map(p => ({ label: p.name, suffix: 'Preset', onSelect: () => go(`/presets/${encodeURIComponent(p.name)}`) })) |
| 58 | } | 58 | } |
| 59 | ] | 59 | ] |
| 60 | : []) | 60 | : []) |
modified
packages/ui/app/components/CatalogBrowser.vue +1 -1 | @@ -58,7 +58,7 @@ const errorsOpen = ref(false) | ||
| 58 | 58 | |
| 59 | 59 | <template> |
| 60 | 60 | <SkPage wide> |
| 61 | - <CatalogHeader tab="items" :new-type="newType" /> | |
| 61 | + <CatalogHeader :new-type="newType" /> | |
| 62 | 62 | |
| 63 | 63 | <div v-if="loading && !data" class="grid gap-4 md:grid-cols-[minmax(200px,280px)_minmax(0,1fr)]"> |
| 64 | 64 | <USkeleton class="h-96 rounded-[20px]" /> |
| @@ -58,7 +58,7 @@ const errorsOpen = ref(false) | |||
| 58 | 58 | ||
| 59 | <template> | 59 | <template> |
| 60 | <SkPage wide> | 60 | <SkPage wide> |
| 61 | - <CatalogHeader tab="items" :new-type="newType" /> | 61 | + <CatalogHeader :new-type="newType" /> |
| 62 | 62 | ||
| 63 | <div v-if="loading && !data" class="grid gap-4 md:grid-cols-[minmax(200px,280px)_minmax(0,1fr)]"> | 63 | <div v-if="loading && !data" class="grid gap-4 md:grid-cols-[minmax(200px,280px)_minmax(0,1fr)]"> |
| 64 | <USkeleton class="h-96 rounded-[20px]" /> | 64 | <USkeleton class="h-96 rounded-[20px]" /> |
modified
packages/ui/app/components/CatalogHeader.vue +5 -16 | @@ -2,21 +2,12 @@ | ||
| 2 | 2 | import type { CatalogItemType } from '@skilly/core' |
| 3 | 3 | |
| 4 | 4 | /** |
| 5 | - * Header shared by every catalog screen: title, "Your shared library", Items | Presets seg and the New button | |
| 6 | - * (split button on Items: the main action creates the current type, the chevron lists every type). | |
| 5 | + * Catalog header: title, "Your shared library" and the New split button (the main action creates the current type, | |
| 6 | + * the chevron lists every type). | |
| 7 | 7 | */ |
| 8 | -const props = defineProps<{ tab: 'items' | 'presets', newType?: CatalogItemType }>() | |
| 8 | +const props = defineProps<{ newType?: CatalogItemType }>() | |
| 9 | 9 | |
| 10 | -const { exists, items, presets, git, data } = useCatalog() | |
| 11 | - | |
| 12 | -const seg = computed({ | |
| 13 | - get: () => props.tab, | |
| 14 | - set: (v: 'items' | 'presets') => { if (v !== props.tab) navigateTo(v === 'items' ? '/catalog' : '/catalog/presets') } | |
| 15 | -}) | |
| 16 | -const options = computed(() => [ | |
| 17 | - { value: 'items' as const, label: 'Items', count: items.value.length }, | |
| 18 | - { value: 'presets' as const, label: 'Presets', count: presets.value.length } | |
| 19 | -]) | |
| 10 | +const { exists, git, data } = useCatalog() | |
| 20 | 11 | |
| 21 | 12 | const where = computed(() => git.value?.remote ?? data.value?.snapshot.root ?? '') |
| 22 | 13 | const type = computed(() => props.newType ?? 'skill') |
| @@ -34,9 +25,7 @@ const menu = computed(() => [CAT_ORDER.map(t => ({ | ||
| 34 | 25 | <span v-if="where" class="font-mono text-xs" :title="where">{{ where }}</span> |
| 35 | 26 | </template> |
| 36 | 27 | <template v-if="exists" #actions> |
| 37 | - <SkSeg v-model="seg" :options="options" aria-label="Catalog section" /> | |
| 38 | - <SkButton v-if="tab === 'presets'" variant="primary" label="New preset" to="/catalog/presets/new" /> | |
| 39 | - <div v-else class="inline-flex"> | |
| 28 | + <div class="inline-flex"> | |
| 40 | 29 | <SkButton variant="primary" :label="CAT_META[type].newLabel" :to="`/catalog/new?type=${type}`" class="rounded-r-none" /> |
| 41 | 30 | <UDropdownMenu :items="menu" :content="{ align: 'end' }"> |
| 42 | 31 | <SkButton variant="primary" icon="i-lucide-chevron-down" aria-label="New item of another type" class="rounded-l-none border-l border-sk-onink/25" /> |
| @@ -2,21 +2,12 @@ | |||
| 2 | import type { CatalogItemType } from '@skilly/core' | 2 | import type { CatalogItemType } from '@skilly/core' |
| 3 | 3 | ||
| 4 | /** | 4 | /** |
| 5 | - * Header shared by every catalog screen: title, "Your shared library", Items | Presets seg and the New button | 5 | + * Catalog header: title, "Your shared library" and the New split button (the main action creates the current type, |
| 6 | - * (split button on Items: the main action creates the current type, the chevron lists every type). | 6 | + * the chevron lists every type). |
| 7 | */ | 7 | */ |
| 8 | -const props = defineProps<{ tab: 'items' | 'presets', newType?: CatalogItemType }>() | 8 | +const props = defineProps<{ newType?: CatalogItemType }>() |
| 9 | 9 | ||
| 10 | -const { exists, items, presets, git, data } = useCatalog() | 10 | +const { exists, git, data } = useCatalog() |
| 11 | - | ||
| 12 | -const seg = computed({ | ||
| 13 | - get: () => props.tab, | ||
| 14 | - set: (v: 'items' | 'presets') => { if (v !== props.tab) navigateTo(v === 'items' ? '/catalog' : '/catalog/presets') } | ||
| 15 | -}) | ||
| 16 | -const options = computed(() => [ | ||
| 17 | - { value: 'items' as const, label: 'Items', count: items.value.length }, | ||
| 18 | - { value: 'presets' as const, label: 'Presets', count: presets.value.length } | ||
| 19 | -]) | ||
| 20 | 11 | ||
| 21 | const where = computed(() => git.value?.remote ?? data.value?.snapshot.root ?? '') | 12 | const where = computed(() => git.value?.remote ?? data.value?.snapshot.root ?? '') |
| 22 | const type = computed(() => props.newType ?? 'skill') | 13 | const type = computed(() => props.newType ?? 'skill') |
| @@ -34,9 +25,7 @@ const menu = computed(() => [CAT_ORDER.map(t => ({ | |||
| 34 | <span v-if="where" class="font-mono text-xs" :title="where">{{ where }}</span> | 25 | <span v-if="where" class="font-mono text-xs" :title="where">{{ where }}</span> |
| 35 | </template> | 26 | </template> |
| 36 | <template v-if="exists" #actions> | 27 | <template v-if="exists" #actions> |
| 37 | - <SkSeg v-model="seg" :options="options" aria-label="Catalog section" /> | 28 | + <div class="inline-flex"> |
| 38 | - <SkButton v-if="tab === 'presets'" variant="primary" label="New preset" to="/catalog/presets/new" /> | ||
| 39 | - <div v-else class="inline-flex"> | ||
| 40 | <SkButton variant="primary" :label="CAT_META[type].newLabel" :to="`/catalog/new?type=${type}`" class="rounded-r-none" /> | 29 | <SkButton variant="primary" :label="CAT_META[type].newLabel" :to="`/catalog/new?type=${type}`" class="rounded-r-none" /> |
| 41 | <UDropdownMenu :items="menu" :content="{ align: 'end' }"> | 30 | <UDropdownMenu :items="menu" :content="{ align: 'end' }"> |
| 42 | <SkButton variant="primary" icon="i-lucide-chevron-down" aria-label="New item of another type" class="rounded-l-none border-l border-sk-onink/25" /> | 31 | <SkButton variant="primary" icon="i-lucide-chevron-down" aria-label="New item of another type" class="rounded-l-none border-l border-sk-onink/25" /> |
modified
packages/ui/app/components/PresetEditor.vue +6 -6 | @@ -131,7 +131,7 @@ async function save() { | ||
| 131 | 131 | }) |
| 132 | 132 | await catalog.refresh() |
| 133 | 133 | original.value = snapshot() |
| 134 | - if (isNew.value || renaming.value) await navigateTo(`/catalog/presets/${encodeURIComponent(form.name)}`, { replace: true }) | |
| 134 | + if (isNew.value || renaming.value) await navigateTo(`/presets/${encodeURIComponent(form.name)}`, { replace: true }) | |
| 135 | 135 | } catch (e) { |
| 136 | 136 | saveError.value = (e as Error).message |
| 137 | 137 | } finally { |
| @@ -140,7 +140,7 @@ async function save() { | ||
| 140 | 140 | } |
| 141 | 141 | |
| 142 | 142 | function cancel() { |
| 143 | - navigateTo('/catalog/presets') | |
| 143 | + navigateTo('/presets') | |
| 144 | 144 | } |
| 145 | 145 | |
| 146 | 146 | onBeforeRouteLeave(() => !dirty.value || window.confirm('Discard unsaved changes?')) |
| @@ -162,7 +162,7 @@ async function doDelete(force = false) { | ||
| 162 | 162 | toast.add({ title: 'Preset deleted', description: props.name, icon: 'i-lucide-check', color: 'success' }) |
| 163 | 163 | delOpen.value = false |
| 164 | 164 | await catalog.refresh() |
| 165 | - await navigateTo('/catalog/presets') | |
| 165 | + await navigateTo('/presets') | |
| 166 | 166 | } catch (e) { |
| 167 | 167 | const err = e as { status?: number, message: string } |
| 168 | 168 | if (err.status === 409) delConflict.value = err.message |
| @@ -179,17 +179,17 @@ function askDelete() { | ||
| 179 | 179 | |
| 180 | 180 | <template> |
| 181 | 181 | <SkPage wide> |
| 182 | - <CatalogHeader tab="presets" /> | |
| 182 | + <PresetsHeader /> | |
| 183 | 183 | |
| 184 | 184 | <USkeleton v-if="!ready" class="h-72 w-full rounded-[20px]" /> |
| 185 | 185 | <SkNote v-else-if="notFound" tone="accent"> |
| 186 | 186 | <p class="m-0 font-medium">Preset not found</p> |
| 187 | 187 | <p class="mt-1 mb-0 text-xs">It may have been renamed or deleted.</p> |
| 188 | - <template #aside><SkButton size="sm" label="Back to presets" to="/catalog/presets" /></template> | |
| 188 | + <template #aside><SkButton size="sm" label="Back to presets" to="/presets" /></template> | |
| 189 | 189 | </SkNote> |
| 190 | 190 | |
| 191 | 191 | <div v-else class="flex flex-col gap-3.5"> |
| 192 | - <SkBreadcrumb :items="[{ label: 'Presets', to: '/catalog/presets' }, { label: isNew ? 'New preset' : name! }]" /> | |
| 192 | + <SkBreadcrumb :items="[{ label: 'Presets', to: '/presets' }, { label: isNew ? 'New preset' : name! }]" /> | |
| 193 | 193 | |
| 194 | 194 | <div class="grid items-start gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(260px,320px)]"> |
| 195 | 195 | <div class="flex min-w-0 flex-col gap-4"> |
| @@ -131,7 +131,7 @@ async function save() { | |||
| 131 | }) | 131 | }) |
| 132 | await catalog.refresh() | 132 | await catalog.refresh() |
| 133 | original.value = snapshot() | 133 | original.value = snapshot() |
| 134 | - if (isNew.value || renaming.value) await navigateTo(`/catalog/presets/${encodeURIComponent(form.name)}`, { replace: true }) | 134 | + if (isNew.value || renaming.value) await navigateTo(`/presets/${encodeURIComponent(form.name)}`, { replace: true }) |
| 135 | } catch (e) { | 135 | } catch (e) { |
| 136 | saveError.value = (e as Error).message | 136 | saveError.value = (e as Error).message |
| 137 | } finally { | 137 | } finally { |
| @@ -140,7 +140,7 @@ async function save() { | |||
| 140 | } | 140 | } |
| 141 | 141 | ||
| 142 | function cancel() { | 142 | function cancel() { |
| 143 | - navigateTo('/catalog/presets') | 143 | + navigateTo('/presets') |
| 144 | } | 144 | } |
| 145 | 145 | ||
| 146 | onBeforeRouteLeave(() => !dirty.value || window.confirm('Discard unsaved changes?')) | 146 | onBeforeRouteLeave(() => !dirty.value || window.confirm('Discard unsaved changes?')) |
| @@ -162,7 +162,7 @@ async function doDelete(force = false) { | |||
| 162 | toast.add({ title: 'Preset deleted', description: props.name, icon: 'i-lucide-check', color: 'success' }) | 162 | toast.add({ title: 'Preset deleted', description: props.name, icon: 'i-lucide-check', color: 'success' }) |
| 163 | delOpen.value = false | 163 | delOpen.value = false |
| 164 | await catalog.refresh() | 164 | await catalog.refresh() |
| 165 | - await navigateTo('/catalog/presets') | 165 | + await navigateTo('/presets') |
| 166 | } catch (e) { | 166 | } catch (e) { |
| 167 | const err = e as { status?: number, message: string } | 167 | const err = e as { status?: number, message: string } |
| 168 | if (err.status === 409) delConflict.value = err.message | 168 | if (err.status === 409) delConflict.value = err.message |
| @@ -179,17 +179,17 @@ function askDelete() { | |||
| 179 | 179 | ||
| 180 | <template> | 180 | <template> |
| 181 | <SkPage wide> | 181 | <SkPage wide> |
| 182 | - <CatalogHeader tab="presets" /> | 182 | + <PresetsHeader /> |
| 183 | 183 | ||
| 184 | <USkeleton v-if="!ready" class="h-72 w-full rounded-[20px]" /> | 184 | <USkeleton v-if="!ready" class="h-72 w-full rounded-[20px]" /> |
| 185 | <SkNote v-else-if="notFound" tone="accent"> | 185 | <SkNote v-else-if="notFound" tone="accent"> |
| 186 | <p class="m-0 font-medium">Preset not found</p> | 186 | <p class="m-0 font-medium">Preset not found</p> |
| 187 | <p class="mt-1 mb-0 text-xs">It may have been renamed or deleted.</p> | 187 | <p class="mt-1 mb-0 text-xs">It may have been renamed or deleted.</p> |
| 188 | - <template #aside><SkButton size="sm" label="Back to presets" to="/catalog/presets" /></template> | 188 | + <template #aside><SkButton size="sm" label="Back to presets" to="/presets" /></template> |
| 189 | </SkNote> | 189 | </SkNote> |
| 190 | 190 | ||
| 191 | <div v-else class="flex flex-col gap-3.5"> | 191 | <div v-else class="flex flex-col gap-3.5"> |
| 192 | - <SkBreadcrumb :items="[{ label: 'Presets', to: '/catalog/presets' }, { label: isNew ? 'New preset' : name! }]" /> | 192 | + <SkBreadcrumb :items="[{ label: 'Presets', to: '/presets' }, { label: isNew ? 'New preset' : name! }]" /> |
| 193 | 193 | ||
| 194 | <div class="grid items-start gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(260px,320px)]"> | 194 | <div class="grid items-start gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(260px,320px)]"> |
| 195 | <div class="flex min-w-0 flex-col gap-4"> | 195 | <div class="flex min-w-0 flex-col gap-4"> |
added
packages/ui/app/components/PresetsHeader.vue +12 -0 | new file mode 100644 | ||
| @@ -0,0 +1,12 @@ | ||
| 1 | +<script setup lang="ts"> | |
| 2 | +/** Presets header: title, what a preset is, and the New preset button (once the catalog exists). */ | |
| 3 | +const { exists } = useCatalog() | |
| 4 | +</script> | |
| 5 | + | |
| 6 | +<template> | |
| 7 | + <SkPageHeader title="Presets" subtitle="Bundles of catalog items you enable together in a project."> | |
| 8 | + <template v-if="exists" #actions> | |
| 9 | + <SkButton variant="primary" label="New preset" to="/presets/new" /> | |
| 10 | + </template> | |
| 11 | + </SkPageHeader> | |
| 12 | +</template> | |
| new file mode 100644 | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | +<script setup lang="ts"> | ||
| 2 | +/** Presets header: title, what a preset is, and the New preset button (once the catalog exists). */ | ||
| 3 | +const { exists } = useCatalog() | ||
| 4 | +</script> | ||
| 5 | + | ||
| 6 | +<template> | ||
| 7 | + <SkPageHeader title="Presets" subtitle="Bundles of catalog items you enable together in a project."> | ||
| 8 | + <template v-if="exists" #actions> | ||
| 9 | + <SkButton variant="primary" label="New preset" to="/presets/new" /> | ||
| 10 | + </template> | ||
| 11 | + </SkPageHeader> | ||
| 12 | +</template> | ||
modified
packages/ui/app/components/scope/ScopeAddPresetsModal.vue +1 -1 | @@ -62,7 +62,7 @@ function toggle(name: string, on: boolean | 'indeterminate') { | ||
| 62 | 62 | </div> |
| 63 | 63 | </div> |
| 64 | 64 | <template #footer> |
| 65 | - <NuxtLink :to="catalogExists ? '/catalog/presets/new' : '/catalog'" class="mr-auto text-sk-muted hover:text-sk-text" @click="open = false"> | |
| 65 | + <NuxtLink :to="catalogExists ? '/presets/new' : '/catalog'" class="mr-auto text-sk-muted hover:text-sk-text" @click="open = false"> | |
| 66 | 66 | {{ catalogExists ? 'Create a new preset' : 'Go to the catalog' }} |
| 67 | 67 | </NuxtLink> |
| 68 | 68 | <SkButton label="Cancel" @click="open = false" /> |
| @@ -62,7 +62,7 @@ function toggle(name: string, on: boolean | 'indeterminate') { | |||
| 62 | </div> | 62 | </div> |
| 63 | </div> | 63 | </div> |
| 64 | <template #footer> | 64 | <template #footer> |
| 65 | - <NuxtLink :to="catalogExists ? '/catalog/presets/new' : '/catalog'" class="mr-auto text-sk-muted hover:text-sk-text" @click="open = false"> | 65 | + <NuxtLink :to="catalogExists ? '/presets/new' : '/catalog'" class="mr-auto text-sk-muted hover:text-sk-text" @click="open = false"> |
| 66 | {{ catalogExists ? 'Create a new preset' : 'Go to the catalog' }} | 66 | {{ catalogExists ? 'Create a new preset' : 'Go to the catalog' }} |
| 67 | </NuxtLink> | 67 | </NuxtLink> |
| 68 | <SkButton label="Cancel" @click="open = false" /> | 68 | <SkButton label="Cancel" @click="open = false" /> |
modified
packages/ui/app/middleware/redirects.global.ts +2 -2 | @@ -1,6 +1,6 @@ | ||
| 1 | 1 | // Old routes kept alive as redirects (bookmarks, links in docs). |
| 2 | 2 | export default defineNuxtRouteMiddleware((to) => { |
| 3 | - if (to.path === '/presets' || to.path.startsWith('/presets/')) { | |
| 4 | - return navigateTo({ path: to.path.replace(/^\/presets/, '/catalog/presets'), query: to.query, hash: to.hash }, { replace: true }) | |
| 3 | + if (to.path === '/catalog/presets' || to.path.startsWith('/catalog/presets/')) { | |
| 4 | + return navigateTo({ path: to.path.replace(/^\/catalog\/presets/, '/presets'), query: to.query, hash: to.hash }, { replace: true }) | |
| 5 | 5 | } |
| 6 | 6 | }) |
| @@ -1,6 +1,6 @@ | |||
| 1 | // Old routes kept alive as redirects (bookmarks, links in docs). | 1 | // Old routes kept alive as redirects (bookmarks, links in docs). |
| 2 | export default defineNuxtRouteMiddleware((to) => { | 2 | export default defineNuxtRouteMiddleware((to) => { |
| 3 | - if (to.path === '/presets' || to.path.startsWith('/presets/')) { | 3 | + if (to.path === '/catalog/presets' || to.path.startsWith('/catalog/presets/')) { |
| 4 | - return navigateTo({ path: to.path.replace(/^\/presets/, '/catalog/presets'), query: to.query, hash: to.hash }, { replace: true }) | 4 | + return navigateTo({ path: to.path.replace(/^\/catalog\/presets/, '/presets'), query: to.query, hash: to.hash }, { replace: true }) |
| 5 | } | 5 | } |
| 6 | }) | 6 | }) |
renamed
packages/ui/app/pages/presets/[name].vue +0 -0 | similarity index 100% | ||
| rename from packages/ui/app/pages/catalog/presets/[name].vue | ||
| rename to packages/ui/app/pages/presets/[name].vue |
| similarity index 100% | |||
| rename from packages/ui/app/pages/catalog/presets/[name].vue | |||
| rename to packages/ui/app/pages/presets/[name].vue |
renamed
packages/ui/app/pages/presets/index.vue +3 -3 | similarity index 97% | ||
| rename from packages/ui/app/pages/catalog/presets/index.vue | ||
| rename to packages/ui/app/pages/presets/index.vue | ||
| @@ -30,7 +30,7 @@ const used = (name: string) => { | ||
| 30 | 30 | |
| 31 | 31 | <template> |
| 32 | 32 | <SkPage wide> |
| 33 | - <CatalogHeader tab="presets" /> | |
| 33 | + <PresetsHeader /> | |
| 34 | 34 | |
| 35 | 35 | <div v-if="loading && !data" class="grid gap-3 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]"> |
| 36 | 36 | <USkeleton v-for="i in 4" :key="i" class="h-32 rounded-[10px]" /> |
| @@ -44,7 +44,7 @@ const used = (name: string) => { | ||
| 44 | 44 | |
| 45 | 45 | <div v-else-if="data" class="grid gap-3 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]"> |
| 46 | 46 | <NuxtLink |
| 47 | - to="/catalog/presets/new" | |
| 47 | + to="/presets/new" | |
| 48 | 48 | class="group flex min-h-[120px] flex-col items-center justify-center gap-2 rounded-[10px] border border-dashed border-sk-faint/50 bg-sk-panel/60 p-4 text-sk-muted transition-colors hover:border-sk-faint hover:bg-sk-panel hover:text-sk-text" |
| 49 | 49 | > |
| 50 | 50 | <span class="grid size-8 place-items-center rounded-full bg-sk-sel text-sk-text transition-colors group-hover:bg-sk-ink group-hover:text-sk-onink"> |
| @@ -56,7 +56,7 @@ const used = (name: string) => { | ||
| 56 | 56 | <NuxtLink |
| 57 | 57 | v-for="p in sorted" |
| 58 | 58 | :key="p.name" |
| 59 | - :to="`/catalog/presets/${encodeURIComponent(p.name)}`" | |
| 59 | + :to="`/presets/${encodeURIComponent(p.name)}`" | |
| 60 | 60 | class="flex flex-col rounded-[10px] bg-sk-panel text-sk-text shadow-sk-ring transition-shadow hover:shadow-sk-ring-hover" |
| 61 | 61 | > |
| 62 | 62 | <div class="flex flex-col gap-2.5 p-4"> |
| similarity index 97% | |||
| rename from packages/ui/app/pages/catalog/presets/index.vue | |||
| rename to packages/ui/app/pages/presets/index.vue | |||
| @@ -30,7 +30,7 @@ const used = (name: string) => { | |||
| 30 | 30 | ||
| 31 | <template> | 31 | <template> |
| 32 | <SkPage wide> | 32 | <SkPage wide> |
| 33 | - <CatalogHeader tab="presets" /> | 33 | + <PresetsHeader /> |
| 34 | 34 | ||
| 35 | <div v-if="loading && !data" class="grid gap-3 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]"> | 35 | <div v-if="loading && !data" class="grid gap-3 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]"> |
| 36 | <USkeleton v-for="i in 4" :key="i" class="h-32 rounded-[10px]" /> | 36 | <USkeleton v-for="i in 4" :key="i" class="h-32 rounded-[10px]" /> |
| @@ -44,7 +44,7 @@ const used = (name: string) => { | |||
| 44 | 44 | ||
| 45 | <div v-else-if="data" class="grid gap-3 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]"> | 45 | <div v-else-if="data" class="grid gap-3 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]"> |
| 46 | <NuxtLink | 46 | <NuxtLink |
| 47 | - to="/catalog/presets/new" | 47 | + to="/presets/new" |
| 48 | class="group flex min-h-[120px] flex-col items-center justify-center gap-2 rounded-[10px] border border-dashed border-sk-faint/50 bg-sk-panel/60 p-4 text-sk-muted transition-colors hover:border-sk-faint hover:bg-sk-panel hover:text-sk-text" | 48 | class="group flex min-h-[120px] flex-col items-center justify-center gap-2 rounded-[10px] border border-dashed border-sk-faint/50 bg-sk-panel/60 p-4 text-sk-muted transition-colors hover:border-sk-faint hover:bg-sk-panel hover:text-sk-text" |
| 49 | > | 49 | > |
| 50 | <span class="grid size-8 place-items-center rounded-full bg-sk-sel text-sk-text transition-colors group-hover:bg-sk-ink group-hover:text-sk-onink"> | 50 | <span class="grid size-8 place-items-center rounded-full bg-sk-sel text-sk-text transition-colors group-hover:bg-sk-ink group-hover:text-sk-onink"> |
| @@ -56,7 +56,7 @@ const used = (name: string) => { | |||
| 56 | <NuxtLink | 56 | <NuxtLink |
| 57 | v-for="p in sorted" | 57 | v-for="p in sorted" |
| 58 | :key="p.name" | 58 | :key="p.name" |
| 59 | - :to="`/catalog/presets/${encodeURIComponent(p.name)}`" | 59 | + :to="`/presets/${encodeURIComponent(p.name)}`" |
| 60 | class="flex flex-col rounded-[10px] bg-sk-panel text-sk-text shadow-sk-ring transition-shadow hover:shadow-sk-ring-hover" | 60 | class="flex flex-col rounded-[10px] bg-sk-panel text-sk-text shadow-sk-ring transition-shadow hover:shadow-sk-ring-hover" |
| 61 | > | 61 | > |
| 62 | <div class="flex flex-col gap-2.5 p-4"> | 62 | <div class="flex flex-col gap-2.5 p-4"> |
renamed
packages/ui/app/pages/presets/new.vue +0 -0 | similarity index 100% | ||
| rename from packages/ui/app/pages/catalog/presets/new.vue | ||
| rename to packages/ui/app/pages/presets/new.vue |
| similarity index 100% | |||
| rename from packages/ui/app/pages/catalog/presets/new.vue | |||
| rename to packages/ui/app/pages/presets/new.vue |