|
1 | 1 | <script lang="ts"> |
2 | 2 | import { onMount, untrack } from 'svelte'; |
3 | 3 | import { fade, fly } from 'svelte/transition'; |
4 | | - import type { App, Config, Group } from '$lib/types'; |
| 4 | + import { type App, type Config, type Group, makeApp, makeGroup, stampAppId, stampGroupId } from '$lib/types'; |
5 | 5 | import IconBrowser from './IconBrowser.svelte'; |
6 | 6 | import AppForm from './AppForm.svelte'; |
7 | 7 | import AppIcon from './AppIcon.svelte'; |
|
89 | 89 | let pendingImport = $state<ImportedConfig | null>(null); |
90 | 90 |
|
91 | 91 | // New app/group templates |
92 | | - const newAppTemplate: App = { |
93 | | - name: '', |
94 | | - url: '', |
95 | | - icon: { type: 'dashboard', name: '', file: '', url: '', variant: '' }, |
96 | | - color: '#22c55e', |
97 | | - group: '', |
98 | | - order: 0, |
99 | | - enabled: true, |
100 | | - default: false, |
101 | | - open_mode: 'iframe', |
102 | | - proxy: false, |
103 | | - scale: 1, |
104 | | - health_check: undefined, |
105 | | - health_url: undefined, |
106 | | - shortcut: undefined, |
107 | | - force_icon_background: undefined, |
108 | | - min_role: undefined, |
109 | | - }; |
110 | | -
|
111 | | - const newGroupTemplate: Group = { |
112 | | - name: '', |
113 | | - icon: { type: 'dashboard', name: '', file: '', url: '', variant: '' }, |
114 | | - color: '#3498db', |
115 | | - order: 0, |
116 | | - expanded: true |
117 | | - }; |
| 92 | + const newAppTemplate: App = makeApp(); |
| 93 | + const newGroupTemplate: Group = makeGroup(); |
118 | 94 |
|
119 | 95 | let newApp = $state({ ...newAppTemplate }); |
120 | 96 | let newGroup = $state({ ...newGroupTemplate }); |
|
132 | 108 | let groupErrors = $state<Record<string, string>>({}); |
133 | 109 |
|
134 | 110 | // Assign stable `id` fields for svelte-dnd-action (must be done once, before building dnd arrays) |
135 | | - untrack(() => localApps).forEach(a => { (a as App & Record<string, unknown>).id = a.name; }); |
136 | | - untrack(() => localConfig).groups.forEach(g => { (g as Group & Record<string, unknown>).id = g.name; }); |
| 111 | + untrack(() => localApps).forEach(stampAppId); |
| 112 | + untrack(() => localConfig).groups.forEach(stampGroupId); |
137 | 113 |
|
138 | 114 | // Snapshot taken AFTER id fields are added, so hasChanges starts as false |
139 | 115 | const initialConfigSnapshot = untrack(() => JSON.stringify(localConfig)); |
|
182 | 158 | for (const apps of Object.values(dndGroupedApps)) { |
183 | 159 | allApps.push(...apps); |
184 | 160 | } |
185 | | - allApps.forEach(a => { (a as App & Record<string, unknown>).id = a.name; }); |
| 161 | + allApps.forEach(stampAppId); |
186 | 162 | localApps = allApps; |
187 | 163 | if (groupName === '__rebuild__') { |
188 | 164 | localConfig.groups = [...dndGroups]; |
|
255 | 231 | } |
256 | 232 | appErrors = {}; |
257 | 233 | newApp.order = localApps.length; |
258 | | - const app: App & Record<string, unknown> = { ...newApp }; |
259 | | - app.id = app.name; |
| 234 | + const app = { ...newApp }; |
| 235 | + stampAppId(app); |
260 | 236 | // Auto-create the group if it doesn't exist yet (e.g. gallery apps with preset groups) |
261 | 237 | if (app.group && !localConfig.groups.some(g => g.name === app.group)) { |
262 | | - localConfig.groups = [...localConfig.groups, { |
263 | | - name: app.group as string, |
264 | | - icon: { type: 'lucide', name: 'folder', file: '', url: '', variant: 'svg', background: '' }, |
| 238 | + const groupName = app.group as string; |
| 239 | + const autoGroup = makeGroup({ |
| 240 | + name: groupName, |
| 241 | + icon: { type: 'lucide', name: 'folder', file: '', url: '', variant: '' }, |
265 | 242 | color: '', |
266 | 243 | order: localConfig.groups.length, |
267 | | - expanded: true, |
268 | | - }]; |
| 244 | + }); |
| 245 | + stampGroupId(autoGroup); |
| 246 | + localConfig.groups = [...localConfig.groups, autoGroup]; |
269 | 247 | } |
270 | 248 | localApps = [...localApps, app]; |
271 | 249 | newApp = { ...newAppTemplate }; |
|
281 | 259 | } |
282 | 260 | groupErrors = {}; |
283 | 261 | newGroup.order = localConfig.groups.length; |
284 | | - const group: Group & Record<string, unknown> = { ...newGroup }; |
285 | | - group.id = group.name; |
| 262 | + const group = { ...newGroup }; |
| 263 | + stampGroupId(group); |
286 | 264 | localConfig.groups = [...localConfig.groups, group]; |
287 | 265 | newGroup = { ...newGroupTemplate }; |
288 | 266 | showAddGroup = false; |
|
307 | 285 | return; |
308 | 286 | } |
309 | 287 | editAppErrors = {}; |
310 | | - (editingApp as App & Record<string, unknown>).id = editingApp.name; |
| 288 | + stampAppId(editingApp); |
311 | 289 | // Sync DnD app changes back to localApps before rebuilding |
312 | 290 | const allApps: App[] = []; |
313 | 291 | for (const apps of Object.values(dndGroupedApps)) { |
|
342 | 320 | return; |
343 | 321 | } |
344 | 322 | editGroupErrors = {}; |
345 | | - (editingGroup as Group & Record<string, unknown>).id = editingGroup.name; |
| 323 | + stampGroupId(editingGroup); |
346 | 324 | // Sync DnD group changes back to localConfig before rebuilding |
347 | 325 | localConfig.groups = [...dndGroups]; |
348 | 326 | } |
|
400 | 378 | localApps = pendingImport.apps; |
401 | 379 |
|
402 | 380 | // Assign stable ids for svelte-dnd-action |
403 | | - localApps.forEach(a => { (a as App & Record<string, unknown>).id = a.name; }); |
404 | | - localConfig.groups.forEach(g => { (g as Group & Record<string, unknown>).id = g.name; }); |
| 381 | + localApps.forEach(stampAppId); |
| 382 | + localConfig.groups.forEach(stampGroupId); |
405 | 383 | rebuildDndArrays(); |
406 | 384 |
|
407 | 385 | showImportConfirm = false; |
|
0 commit comments