hlassiege/panopliepublic Fork
⑂ 30ddc7a
Commits
⬇ Clone ▾
git clone https://git.rickub.com/hlassiege/panoplie.git
git clone ssh://git@rickub.com/hlassiege/panoplie.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

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>
hlassiege committed 2026-10-23T17:33:37+02:00 Browse files
30ddc7a parent: 9e4bfeb
modified packages/ui/app/components/AppHeader.vue +2 -1
@@ -7,7 +7,7 @@
77 const route = useRoute()
88 const { projects } = useProjects()
99 const { home, refresh: refreshHome } = useHomeInventory()
10-const { items, exists, git } = useCatalog()
10+const { items, presets, exists, git } = useCatalog()
1111 const { busy, run } = useCatalogGit()
1212 const colorMode = useColorMode()
1313 const switcher = useSwitcher()
@@ -22,6 +22,7 @@ const nav = computed<NavItem[]>(() => [
2222 // Global (the Home scope) is the pinned first row of Projects.
2323 { label: 'Projects', to: '/projects', count: projects.value.length },
2424 { label: 'Catalog', to: '/catalog', count: exists.value ? items.value.length : undefined },
25+ { label: 'Presets', to: '/presets', count: exists.value ? presets.value.length : undefined },
2526 { label: 'Marketplaces', to: '/marketplaces' },
2627 { label: 'Sync', to: '/sync' }
2728 ])
@@ -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 = [
1919 { label: 'Global', to: GLOBAL_PATH, description: 'Loaded in every project (~)' },
2020 { label: 'Projects', to: '/projects' },
2121 { label: 'Catalog', to: '/catalog' },
22- { label: 'Presets', to: '/catalog/presets' },
22+ { label: 'Presets', to: '/presets' },
2323 { label: 'Marketplaces', to: '/marketplaces' },
2424 { label: 'Sync', to: '/sync' },
2525 { label: 'Settings', to: '/settings' }
@@ -54,7 +54,7 @@ const groups = computed(() => [
5454 {
5555 id: 'presets',
5656 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)}`) }))
5858 }
5959 ]
6060 : [])
@@ -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)
5858
5959 <template>
6060 <SkPage wide>
61- <CatalogHeader tab="items" :new-type="newType" />
61+ <CatalogHeader :new-type="newType" />
6262
6363 <div v-if="loading && !data" class="grid gap-4 md:grid-cols-[minmax(200px,280px)_minmax(0,1fr)]">
6464 <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 @@
22 import type { CatalogItemType } from '@skilly/core'
33
44 /**
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).
77 */
8-const props = defineProps<{ tab: 'items' | 'presets', newType?: CatalogItemType }>()
8+const props = defineProps<{ newType?: CatalogItemType }>()
99
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()
2011
2112 const where = computed(() => git.value?.remote ?? data.value?.snapshot.root ?? '')
2213 const type = computed(() => props.newType ?? 'skill')
@@ -34,9 +25,7 @@ const menu = computed(() => [CAT_ORDER.map(t => ({
3425 <span v-if="where" class="font-mono text-xs" :title="where">{{ where }}</span>
3526 </template>
3627 <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">
4029 <SkButton variant="primary" :label="CAT_META[type].newLabel" :to="`/catalog/new?type=${type}`" class="rounded-r-none" />
4130 <UDropdownMenu :items="menu" :content="{ align: 'end' }">
4231 <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 button5+ * 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() {
131131 })
132132 await catalog.refresh()
133133 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 })
135135 } catch (e) {
136136 saveError.value = (e as Error).message
137137 } finally {
@@ -140,7 +140,7 @@ async function save() {
140140 }
141141
142142 function cancel() {
143- navigateTo('/catalog/presets')
143+ navigateTo('/presets')
144144 }
145145
146146 onBeforeRouteLeave(() => !dirty.value || window.confirm('Discard unsaved changes?'))
@@ -162,7 +162,7 @@ async function doDelete(force = false) {
162162 toast.add({ title: 'Preset deleted', description: props.name, icon: 'i-lucide-check', color: 'success' })
163163 delOpen.value = false
164164 await catalog.refresh()
165- await navigateTo('/catalog/presets')
165+ await navigateTo('/presets')
166166 } catch (e) {
167167 const err = e as { status?: number, message: string }
168168 if (err.status === 409) delConflict.value = err.message
@@ -179,17 +179,17 @@ function askDelete() {
179179
180180 <template>
181181 <SkPage wide>
182- <CatalogHeader tab="presets" />
182+ <PresetsHeader />
183183
184184 <USkeleton v-if="!ready" class="h-72 w-full rounded-[20px]" />
185185 <SkNote v-else-if="notFound" tone="accent">
186186 <p class="m-0 font-medium">Preset not found</p>
187187 <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>
189189 </SkNote>
190190
191191 <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! }]" />
193193
194194 <div class="grid items-start gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(260px,320px)]">
195195 <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).message136 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 = false163 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.message168 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') {
6262 </div>
6363 </div>
6464 <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">
6666 {{ catalogExists ? 'Create a new preset' : 'Go to the catalog' }}
6767 </NuxtLink>
6868 <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 @@
11 // Old routes kept alive as redirects (bookmarks, links in docs).
22 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 })
55 }
66 })
@@ -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) => {
3030
3131 <template>
3232 <SkPage wide>
33- <CatalogHeader tab="presets" />
33+ <PresetsHeader />
3434
3535 <div v-if="loading && !data" class="grid gap-3 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]">
3636 <USkeleton v-for="i in 4" :key="i" class="h-32 rounded-[10px]" />
@@ -44,7 +44,7 @@ const used = (name: string) => {
4444
4545 <div v-else-if="data" class="grid gap-3 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]">
4646 <NuxtLink
47- to="/catalog/presets/new"
47+ to="/presets/new"
4848 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"
4949 >
5050 <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) => {
5656 <NuxtLink
5757 v-for="p in sorted"
5858 :key="p.name"
59- :to="`/catalog/presets/${encodeURIComponent(p.name)}`"
59+ :to="`/presets/${encodeURIComponent(p.name)}`"
6060 class="flex flex-col rounded-[10px] bg-sk-panel text-sk-text shadow-sk-ring transition-shadow hover:shadow-sk-ring-hover"
6161 >
6262 <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 <NuxtLink46 <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 <NuxtLink56 <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