@xen-orchestra/web-core 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/lib/components/backdrop/VtsBackdrop.vue +1 -1
  2. package/lib/components/column/VtsColumn.vue +21 -0
  3. package/lib/components/columns/VtsColumns.vue +38 -0
  4. package/lib/components/copy-button/VtsCopyButton.vue +29 -0
  5. package/lib/components/enabled-state/VtsEnabledState.vue +23 -0
  6. package/lib/components/icon/VtsIcon.vue +9 -1
  7. package/lib/components/input-wrapper/VtsInputWrapper.vue +1 -0
  8. package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
  9. package/lib/components/quick-info-column/VtsQuickInfoColumn.vue +1 -1
  10. package/lib/components/quick-info-row/VtsQuickInfoRow.vue +26 -7
  11. package/lib/components/relative-time/VtsRelativeTime.vue +18 -0
  12. package/lib/components/select/VtsOption.vue +24 -0
  13. package/lib/components/select/VtsSelect.vue +96 -0
  14. package/lib/components/state-hero/VtsLoadingHero.vue +45 -4
  15. package/lib/components/tree/VtsTreeItem.vue +11 -1
  16. package/lib/components/ui/alert/UiAlert.vue +105 -0
  17. package/lib/components/ui/circle-progress-bar/UiCircleProgressBar.vue +212 -0
  18. package/lib/components/ui/dropdown/UiDropdownList.vue +10 -2
  19. package/lib/components/ui/head-bar/UiHeadBar.vue +2 -2
  20. package/lib/components/ui/info/UiInfo.vue +5 -3
  21. package/lib/components/ui/input/UiInput.vue +126 -109
  22. package/lib/composables/chart-theme.composable.ts +3 -3
  23. package/lib/composables/relative-time.composable.ts +1 -1
  24. package/lib/i18n.ts +4 -0
  25. package/lib/locales/cs.json +65 -18
  26. package/lib/locales/en.json +65 -1
  27. package/lib/locales/es.json +60 -13
  28. package/lib/locales/fa.json +59 -12
  29. package/lib/locales/fr.json +67 -3
  30. package/lib/locales/it.json +145 -7
  31. package/lib/locales/nl.json +502 -0
  32. package/lib/locales/ru.json +91 -1
  33. package/lib/locales/sv.json +75 -19
  34. package/lib/packages/collection/README.md +172 -0
  35. package/lib/packages/collection/create-collection.ts +74 -0
  36. package/lib/packages/collection/create-item.ts +39 -0
  37. package/lib/packages/collection/guess-item-id.ts +26 -0
  38. package/lib/packages/collection/index.ts +2 -0
  39. package/lib/packages/collection/types.ts +57 -0
  40. package/lib/packages/collection/use-collection.ts +47 -0
  41. package/lib/packages/collection/use-flag-registry.ts +64 -0
  42. package/lib/packages/form-select/README.md +96 -0
  43. package/lib/packages/form-select/index.ts +2 -0
  44. package/lib/packages/form-select/types.ts +75 -0
  45. package/lib/packages/form-select/use-form-option-controller.ts +50 -0
  46. package/lib/packages/form-select/use-form-select-controller.ts +205 -0
  47. package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +157 -0
  48. package/lib/packages/form-select/use-form-select.ts +193 -0
  49. package/lib/stores/sidebar.store.ts +14 -1
  50. package/package.json +1 -1
@@ -6,10 +6,11 @@
6
6
  "add": "Lägg till",
7
7
  "add-filter": "Lägg till filter",
8
8
  "add-or": "+ELLER",
9
- "add-sort": "Lägg till soterting",
9
+ "add-sort": "Lägg till sortering",
10
10
  "admin-login": "Admin-inloggning",
11
11
  "admin-password": "Admin-lösenord",
12
12
  "admin-password-confirm": "Bekräfta admin-lösenord",
13
+ "affinity-host": "Fokus-host",
13
14
  "alarm-type.cpu_usage": "CPU-användning överskrider {n}%",
14
15
  "alarm-type.disk_usage": "Disk-användning överskrider {n}%",
15
16
  "alarm-type.fs_usage": "FS-användning överskrider {n}%",
@@ -17,14 +18,16 @@
17
18
  "alarm-type.mem_usage": "Minnes-användning överskrider {n}%",
18
19
  "alarm-type.memory_free_kib": "Ledigt minne är under {n}%",
19
20
  "alarm-type.network_usage": "Nätverks-användning överskrider {n}%",
20
- "alarm-type.physical_utilisation": "Fysisktnyttjande överskrider {n}%",
21
- "alarm-type.sr_io_throughput_total_per_host": "SR IO genomförning totalt per host överskrider {n}%",
21
+ "alarm-type.physical_utilisation": "Fysiskt nyttjande överskrider {n}%",
22
+ "alarm-type.sr_io_throughput_total_per_host": "SR IO genomströmning totalt per host överskrider {n}%",
22
23
  "alarm-type.unknown": "Okänd larm-typ",
23
24
  "alarms": "Larm",
24
25
  "all-good": "Allt är bra!",
25
26
  "allow-self-signed-ssl": "Du kan behöva tillåta själv-signerade SSL-certifikat i din webbläsare",
26
27
  "appearance": "Utseende",
27
28
  "ascending": "stigande",
29
+ "auto-generated": "Automatiskt generad",
30
+ "auto-power": "Automatisk strömpåslagning",
28
31
  "available": "Tillgänglig",
29
32
  "available-properties-for-advanced-filter": "Tillgängliga egenskaper för avancerade filter:",
30
33
  "back-pool-dashboard": "Gå tillbaka till din Pool-översikt",
@@ -44,15 +47,21 @@
44
47
  "backups.vms-protection.protected": "I minst ett jobb och skyddad",
45
48
  "backups.vms-protection.tooltip": "En VM är skyddad om den är med i ett säkerhetskopieringsjobb, med ett aktivt schema och den senaste körningen lyckades",
46
49
  "backups.vms-protection.unprotected": "Med i minst 1 jobb men oskyddad",
50
+ "bios-default": "bios (standardinställt)",
47
51
  "bond": "Bindning",
48
52
  "bond-devices": "Bindningsenheter",
49
53
  "bond-status": "Bindnings-status",
54
+ "boot-firmware": "Boot-programvara",
55
+ "boot-firmware-bios": "Den här mallen innehåller redan Bios-strängar",
56
+ "boot-firmware-uefi": "Boot-programvaran är UEFI",
57
+ "boot-vm": "Starta VM",
50
58
  "bytes.gi": "GiB",
51
59
  "bytes.ki": "KiB",
52
60
  "bytes.mi": "MiB",
53
61
  "cancel": "Avbryt",
54
62
  "change-state": "Ändra",
55
63
  "check-errors": "Kontrollera felen:",
64
+ "check-summing": "TX checksumma",
56
65
  "click-to-display-alarms": "Klicka för att visa larm:",
57
66
  "click-to-return-default-pool": "Klicka här för att återgå till standardpoolen",
58
67
  "close": "Stäng",
@@ -68,6 +77,8 @@
68
77
  "console-clipboard": "Konsolurklipp",
69
78
  "console-unavailable": "Konsolen är inte tillgänglig",
70
79
  "copy": "Kopiera",
80
+ "copy-all": "Kopiera allt",
81
+ "copy-host": "Kopiera värddator",
71
82
  "copy-info-json": "Kopiera informationen till JSON",
72
83
  "core.character-limit": "{count}/{max} tecken | {count}/{max} tecken",
73
84
  "core.close": "Stäng",
@@ -78,6 +89,8 @@
78
89
  "core.group": "Grupp",
79
90
  "core.hide": "Göm",
80
91
  "core.open": "Öppna",
92
+ "core.pagination.all": "Alla",
93
+ "core.pagination.show-by": "Sortera efter",
81
94
  "core.query-search-bar.label": "Sökmotor",
82
95
  "core.query-search-bar.placeholder": "Skriv din fråga…",
83
96
  "core.query-search-bar.use-query-builder": "Använd frågebyggaren",
@@ -96,10 +109,12 @@
96
109
  "core.sort.ascending": "Sortera stigande",
97
110
  "core.sort.descending": "Sotera fallande",
98
111
  "core.textarea.exceeds-max-characters": "Fältet får enbart vara {max} tecken eller färre.",
99
- "cores-with-sockets": "",
112
+ "cores-with-sockets": "Kärnor (sockets)",
100
113
  "cpu-provisioning": "CPU-provisionering",
101
114
  "cpu-provisioning-warning": "Antalet vCPU:er som allokerats överskrider antalet fysiska CPU:er. Systemets prestanda kan påverkas",
102
115
  "cpu-usage": "CPU-nyttjande",
116
+ "create": "Skapa",
117
+ "custom-config": "Skräddarsydd konfiguration",
103
118
  "dark-mode.auto": "Automatiskt mörkt läge",
104
119
  "dark-mode.disable": "Inaktivera mörkt läge",
105
120
  "dark-mode.enable": "Aktivera mörkt läge",
@@ -124,6 +139,7 @@
124
139
  "disabled": "Inaktiverad",
125
140
  "disconnected": "Frånkopplad",
126
141
  "disconnected-from-physical-device": "Koppla från fysisk enhet",
142
+ "disk-name": "Disk-namn",
127
143
  "display": "Display",
128
144
  "dns": "DNS",
129
145
  "do-you-have-needs": "Har du behov och/eller förväntningar? Berätta för oss",
@@ -144,6 +160,7 @@
144
160
  "export-vm": "Exportera VM",
145
161
  "export-vms": "Exportera VMar",
146
162
  "export-vms-manually-information": "Vissa VM-exporter kunde inte startas automatiskt, troligen på grund av dina webbläsarinställningar. För att exportera dem, klicka på varje VM. (Alternativt, kopiera länken.)",
163
+ "fast-clone": "Snabb-kloning",
147
164
  "fetching-fresh-data": "Hämtar nya data",
148
165
  "filter.comparison.contains": "Innehåller",
149
166
  "filter.comparison.ends-with": "Slutar med",
@@ -169,15 +186,18 @@
169
186
  "gateway": "Gateway",
170
187
  "go-back": "Gå tillbaka",
171
188
  "gzip": "gzip",
172
- "hardware": "",
189
+ "hardware": "Hårdvara",
173
190
  "here": "Här",
174
191
  "host": "Host",
175
192
  "host-description": "Host-beskrivning",
176
- "host-internal-networks": "Hostinternanätverk",
193
+ "host-internal-networks": "Host-interna nätverk",
194
+ "host-status.halted": "Stoppad",
195
+ "host-status.running": "Kör",
196
+ "host-status.unknown": "Okänt",
177
197
  "host-unknown": "Host okänd",
178
198
  "host.active": "Aktiv",
179
199
  "host.inactive": "Inaktiv",
180
- "hosts": "Hosts",
200
+ "hosts": "Hostar",
181
201
  "hosts-status": "Host-status",
182
202
  "hosts-status.halted": "Stoppad",
183
203
  "hosts-status.running": "Igång",
@@ -185,14 +205,18 @@
185
205
  "hosts-status.unknown.tooltip": "Host-data är otillgängligt",
186
206
  "id": "Id",
187
207
  "in-last-three-jobs": "I de senaste tre jobben",
208
+ "install-settings": "Installationsinställningar",
209
+ "interfaces": "Gränssnitt | Gränssnitt | Gränssnitten",
188
210
  "invalid-field": "Felaktigt fält",
211
+ "ip-address": "IP-adress",
189
212
  "ip-addresses": "IP-adresser",
190
213
  "ip-mode": "IP-läge",
191
- "is-primary-host": "",
214
+ "is-primary-host": "{name} är den primära hosten",
215
+ "iso-dvd": "ISO/DVD",
192
216
  "job.vm-copy.bad-power-state": "VMen måste vara stoppad",
193
217
  "job.vm-copy.in-progress": "Kopiering pågår…",
194
218
  "job.vm-copy.missing-vms": "Ingen VM att kopiera",
195
- "job.vm-delete.bad-power-state": "Vmen måste vara stoppad",
219
+ "job.vm-delete.bad-power-state": "VMen måste vara stoppad",
196
220
  "job.vm-delete.in-progress": "Radering pågår…",
197
221
  "job.vm-delete.missing-vms": "Ingen VM att radera",
198
222
  "job.vm-export.in-progress": "Export pågår…",
@@ -238,7 +262,7 @@
238
262
  "language": "Språk",
239
263
  "last-week": "Föregående vecka",
240
264
  "learn-more": "Läs mer",
241
- "load-average": "",
265
+ "load-average": "Genomsnittslast",
242
266
  "load-now": "Ladda nu",
243
267
  "loading-hosts": "Ladda hostar…",
244
268
  "loading-in-progress": "Laddning pågår…",
@@ -251,12 +275,14 @@
251
275
  "mac-addresses": "MAC-adresser",
252
276
  "management": "Hantering",
253
277
  "master": "Primärhost",
254
- "memory-usage": "",
278
+ "memory": "Minne",
279
+ "memory-usage": "Minnesanvändning",
255
280
  "migrate": "Migrera",
256
281
  "migrate-n-vms": "Migrera 1 VM | Migrera {n} VMar",
257
282
  "missing-patches": "Saknar uppdateringar",
258
283
  "more-actions": "Fler åtgärder",
259
284
  "mtu": "MTU",
285
+ "multi-creation": "Multi-skapande",
260
286
  "n-gb-left": "{n} GB kvar",
261
287
  "n-gb-required": "{n} GB behövs",
262
288
  "n-hosts": "1 host | {n} hostar",
@@ -266,7 +292,8 @@
266
292
  "name": "Namn",
267
293
  "netmask": "Nätmaskadress",
268
294
  "network": "Nätverk",
269
- "network-block-device": "Nätverksblockeringsenhet",
295
+ "network-block-device": "Nätverksblockenhet",
296
+ "network-config": "Nätverkskonfiguration",
270
297
  "network-download": "Ladda ned",
271
298
  "network-information": "Nätverksinformation",
272
299
  "network-throughput": "Nätverksflöde",
@@ -274,17 +301,24 @@
274
301
  "networks": "Nätverk",
275
302
  "new": "Ny",
276
303
  "new-features-are-coming": "Nya funktioner kommer snart!",
304
+ "new-vif": "Ny VIF",
305
+ "new-vm": "Ny VM",
306
+ "new-vm.add": "Lägg till ny VM",
307
+ "new-vm.description": "VM-beskrivning",
308
+ "new-vm.name": "VM-namn",
277
309
  "news": "Nyheter",
278
310
  "news-name": "{name} nyheter",
279
311
  "no-alarm-triggered": "Inga larm",
312
+ "no-config": "Ingen konfiguration",
280
313
  "no-data": "Ingen data",
281
314
  "no-network-detected": "Inga nätverk hittade",
282
- "no-pif-detected": "Ingen pif hittad",
315
+ "no-pif-detected": "Ingen PIF hittad",
283
316
  "no-result": "Inget resultat",
284
317
  "no-results": "Inga resultat",
285
318
  "no-selected-vm-can-be-exported": "Inga markerade VMar kan exporteras",
286
319
  "no-selected-vm-can-be-migrated": "Inga markerade VMar kan migreras",
287
320
  "no-tasks": "Inga uppgifter",
321
+ "no-vif-detected": "Ingen VIF hittades",
288
322
  "none": "Inga",
289
323
  "not-found": "Hittades inte",
290
324
  "object": "Objekt",
@@ -303,9 +337,10 @@
303
337
  "patches": "Patchar",
304
338
  "pause": "Pausa",
305
339
  "physical-interface-status": "Status för fysiska gränssnitt",
340
+ "pick-template": "Välj mall",
306
341
  "pif": "PIF",
307
342
  "pif-status": "PIF-status",
308
- "pifs": "PIFs",
343
+ "pifs": "PIFar",
309
344
  "pifs-status": "PIF status",
310
345
  "please-confirm": "Vänligen bekräfta",
311
346
  "pool-cpu-usage": "Användning av CPU-pool",
@@ -323,7 +358,9 @@
323
358
  "professional-support": "Professionell support",
324
359
  "properties": "Egenskaper",
325
360
  "property": "Egenskap",
326
- "ram": "",
361
+ "pxe": "PXE",
362
+ "quick-info": "Snabbinfo",
363
+ "ram": "RAM",
327
364
  "ram-usage": "RAM-användning",
328
365
  "reboot": "Starta om",
329
366
  "receive": "Ta emot",
@@ -346,6 +383,7 @@
346
383
  "see-all": "Se alla",
347
384
  "select-compression": "Välj en komprimering",
348
385
  "select-destination-host": "Välj en destinationshost",
386
+ "select-host": "Välj host",
349
387
  "select-to-see-details": "Välj ett objekt för att se detaljer",
350
388
  "select.network": "Välj ett nätverk",
351
389
  "select.storage": "Välj en lagring",
@@ -357,10 +395,14 @@
357
395
  "shutdown": "Stäng av",
358
396
  "sidebar.search-tree-view": "Sök i trädvyn",
359
397
  "sidebar.vms-treeview": "VM-trädvy",
398
+ "size": "Storlek",
360
399
  "snapshot": "Snapshot",
400
+ "sockets-with-cores-per-socket": "{nSockets} socklar × {nCores} kärnor/sockel",
361
401
  "sort-by": "Sortera efter",
362
402
  "speed": "Hastighet",
403
+ "sr": "SR",
363
404
  "ssh-account": "SSH-konto",
405
+ "ssh-key": "SSH-nyckel",
364
406
  "ssh-login": "SSH-inloggning",
365
407
  "ssh-password": "SSH-lösenord",
366
408
  "ssh-password-confirm": "Verifiera SSH-lösenord",
@@ -368,14 +410,17 @@
368
410
  "stacked-ram-usage": "Staplad RAM-användning",
369
411
  "start": "Starta",
370
412
  "start-on-host": "Starta på specifik host",
371
- "started": "",
413
+ "started": "Startad",
414
+ "state": "Status",
372
415
  "static": "Statisk",
373
416
  "static-ip": "Statisk IP",
374
417
  "stats": "Statistik",
375
418
  "status": "Status",
376
419
  "storage": "Lagring",
420
+ "storage-repositories": "Lagringsförvar",
377
421
  "storage-repository": "Lagringsförvar",
378
422
  "storage-usage": "Lagringsanvändande",
423
+ "summary": "Summering",
379
424
  "support": "Support",
380
425
  "suspend": "Suspendera",
381
426
  "switch-theme": "Byt tema",
@@ -383,7 +428,7 @@
383
428
  "table-actions": "Tabellåtgärder",
384
429
  "tags": "Taggar",
385
430
  "task.estimated-end": "Estimerad sluttid",
386
- "task.progress": "Status",
431
+ "task.progress": "Framsteg",
387
432
  "task.started": "Startad",
388
433
  "tasks": "Uppgifter",
389
434
  "tasks.n-subtasks": "{n} underuppgift | {n} underuppgifter",
@@ -393,18 +438,20 @@
393
438
  "tasks.quick-view.done": "Klar",
394
439
  "tasks.quick-view.failed": "Misslyckades",
395
440
  "tasks.quick-view.in-progress": "Pågår",
441
+ "template": "Mall",
396
442
  "theme-auto": "Auto",
397
443
  "theme-dark": "Mörkt",
398
444
  "theme-light": "Ljust",
399
445
  "this-vm-cant-be-migrated": "Denna VM kan inte migreras",
400
446
  "top-#": "Topp {n}",
447
+ "topology": "Topologi",
401
448
  "total": "Totalt",
402
449
  "total-cpus": "Totalt antal CPUer",
403
- "total-free": "",
450
+ "total-free": "Totalt ledigt",
404
451
  "total-free:": "Totalt ledigt:",
405
452
  "total-memory": "Totalt minne",
406
453
  "total-storage-repository": "Totalt lagringsutrymme",
407
- "total-used": "",
454
+ "total-used": "Totalt använt",
408
455
  "total-used:": "Totalt använt:",
409
456
  "unknown": "Okänt",
410
457
  "unlocked": "Upplåst",
@@ -412,10 +459,16 @@
412
459
  "unreachable-hosts-reload-page": "Färdigt, ladda om sidan",
413
460
  "up-to-date": "Uppdaterad",
414
461
  "used": "Använt",
462
+ "user-config": "Användarkonfiguration",
415
463
  "uuid": "UUID",
416
464
  "vcpus": "vCPUer",
417
465
  "vcpus-used": "vCPUer använt",
466
+ "vdis": "VDI | VDI | VDIer",
418
467
  "version": "Version",
468
+ "vif": "VIF",
469
+ "vif-device": "VIF #{device}",
470
+ "vif-status": "VIF-status",
471
+ "vifs": "VIFar",
419
472
  "vlan": "VLAN",
420
473
  "vm": "VM",
421
474
  "vm-description": "VM beskrivning",
@@ -424,9 +477,12 @@
424
477
  "vm.inactive": "Inaktiv",
425
478
  "vms": "VMar",
426
479
  "vms-status": "VM status",
480
+ "vms-status.halted": "Stannad",
427
481
  "vms-status.inactive": "Inaktiv",
428
482
  "vms-status.inactive.tooltip": "Avstängd eller suspenderad",
483
+ "vms-status.paused": "Pausad",
429
484
  "vms-status.running": "Igång",
485
+ "vms-status.suspended": "Suspenderad",
430
486
  "vms-status.unknown": "Okänt",
431
487
  "vms-status.unknown.tooltip": "För vilken XO har förlorat kontakten med poolen",
432
488
  "xo-backups": "XO-backuper",
@@ -0,0 +1,172 @@
1
+ # `useCollection` composable
2
+
3
+ The `useCollection` composable helps you manage a collection of items with flags (boolean states) and computed properties. It provides a type-safe way to filter, flag, and manipulate items in a collection.
4
+
5
+ ## Usage
6
+
7
+ ```typescript
8
+ const { items, useSubset, useFlag } = useCollection(sources, {
9
+ flags: ['selected', 'active', { highlighted: { multiple: false } }],
10
+ properties: source => ({
11
+ id: source.theId, // Required if TSource doesn't have an `id` property
12
+ isAvailable: source.status === 'available',
13
+ fullName: `${source.firstName} ${source.lastName}`,
14
+ }),
15
+ })
16
+ ```
17
+
18
+ ## Core Concepts
19
+
20
+ - **Collection Item**: An object with a unique identifier, a reference to its source object, flags, computed properties, and methods to manipulate flags
21
+ - **Flags**: Boolean states attached to items (like 'selected', 'active', 'highlighted')
22
+ - **Properties**: Additional custom values
23
+
24
+ ## `useCollection` parameters
25
+
26
+ | Name | Type | Required | Description |
27
+ | --------- | ------------------------------------------------ | :------: | ---------------------------------------------------- |
28
+ | `sources` | `MaybeRefOrGetter<TSource[]>` | ✓ | Array of source objects for the collection |
29
+ | `options` | `CollectionOptions<TSource, TFlag, TProperties>` | ~ | Configuration options for the collection (see below) |
30
+
31
+ ### `options` object
32
+
33
+ | Name | Type | Required | Description |
34
+ | ------------ | ---------------------------------------------- | :------: | --------------------------------------------------------------------- |
35
+ | `flags` | `FlagsConfig<TFlag>` | | Flags that can be applied to items in the collection |
36
+ | `properties` | `(source: TSource) => Record<string, unknown>` | ~ | Function that returns additional properties for each item (see below) |
37
+
38
+ ### Item ID
39
+
40
+ The item ID will be retrieved automatically from `TSource.id`
41
+
42
+ If `TSource` doesn't provide an `id`, then `options.properties` will be required and must return at least an `id`
43
+
44
+ ### `FlagsConfig` type
45
+
46
+ ```typescript
47
+ type FlagsConfig<TFlag extends string> = TFlag[] | { [K in TFlag]: { multiple?: boolean } }
48
+ ```
49
+
50
+ Values for `multiple`:
51
+
52
+ - `true` (default): multiple items can share the same flag.
53
+ - `false`: only one item can have the flag at a time (exclusive selection). Setting the flag on one item will automatically unset it on all other items.
54
+
55
+ ## Return Value
56
+
57
+ | Name | Type | Description |
58
+ | ----------- | ------------------------------------------- | --------------------------------------------------- |
59
+ | `items` | `ComputedRef<CollectionItem[]>` | Array of collection items with flags and properties |
60
+ | `useSubset` | `(filter: (item) => boolean) => Collection` | Creates a sub collection matching the filter |
61
+ | `useFlag` | `(flag: TFlag) => UseFlagReturn` | Utilities for working with a specific flag |
62
+ | `count` | `ComputedRef<number>` | Number of items in the collection |
63
+
64
+ ### `CollectionItem` object
65
+
66
+ | Name | Type | Description |
67
+ | ------------ | ------------------------------ | --------------------------------------------------------- |
68
+ | `id` | `TId` | Unique identifier for the item (string, number or symbol) |
69
+ | `source` | `TSource` | The original source object |
70
+ | `flags` | `Record<TFlag, boolean>` | Object containing the state of all flags for this item |
71
+ | `properties` | `TProperties` | Object containing all computed properties for this item |
72
+ | `toggleFlag` | `(flag, forcedValue?) => void` | Method to toggle a flag on this item |
73
+
74
+ ### UseFlagReturn object
75
+
76
+ | Name | Type | Description |
77
+ | ----------- | ------------------------------------------- | ------------------------------------------------------ |
78
+ | `items` | `ComputedRef<CollectionItem[]>` | Array of items that have this flag set |
79
+ | `ids` | `ComputedRef<TId[]>` | Array of IDs of items that have this flag set |
80
+ | `count` | `ComputedRef<number>` | Number of items that have this flag set |
81
+ | `areAllOn` | `ComputedRef<boolean>` | Whether all items in the collection have this flag set |
82
+ | `areSomeOn` | `ComputedRef<boolean>` | Whether at least one item has this flag set |
83
+ | `areNoneOn` | `ComputedRef<boolean>` | Whether no items have this flag set |
84
+ | `toggle` | `(id, forcedValue?) => void` | Toggle this flag on a specific item |
85
+ | `toggleAll` | `(forcedValue?) => void` | Toggle this flag on all items in the collection |
86
+ | `useSubset` | `(filter: (item) => boolean) => Collection` | Creates a sub collection matching the filter |
87
+
88
+ ## Flag Operations
89
+
90
+ The composable provides several ways to work with flags:
91
+
92
+ ### Setting and Toggling Flags
93
+
94
+ You can work with flags directly on collection items:
95
+
96
+ ```typescript
97
+ // Set flag for a single item
98
+ item.flags.selected = true
99
+
100
+ // Toggle the 'selected' flag on an item
101
+ // Same as item.flags.selected = !item.flags.selected
102
+ item.toggleFlag('selected')
103
+
104
+ // Force the 'selected' flag to a specific value
105
+ // Same as item.flags.selected = true
106
+ item.toggleFlag('selected', true)
107
+ ```
108
+
109
+ You can also use the utilities provided by `useFlag`:
110
+
111
+ ```typescript
112
+ // Get utilities for the 'selected' flag
113
+ const { toggle: toggleSelected, toggleAll: toggleAllSelected } = useFlag('selected')
114
+
115
+ // Toggle flag for a specific item
116
+ toggleSelected(itemId)
117
+
118
+ // Force flag to true for a specific item
119
+ toggleSelected(itemId, true)
120
+
121
+ // Toggle flag on all items
122
+ toggleAllSelected()
123
+
124
+ // Force all items to have the flag
125
+ toggleAllSelected(true)
126
+ ```
127
+
128
+ ### Checking Flags
129
+
130
+ You can check if an item has a flag:
131
+
132
+ ```typescript
133
+ // Check if an item has a specific flag
134
+ const isSelected = item.flags.selected
135
+ ```
136
+
137
+ ## Example
138
+
139
+ ```typescript
140
+ const {
141
+ items: users,
142
+ useSubset,
143
+ count,
144
+ } = useCollection(rawUsers, {
145
+ flags: ['selected'],
146
+ properties: user => ({
147
+ fullName: `${user.firstName} ${user.lastName} (${user.group})`,
148
+ }),
149
+ })
150
+
151
+ const { items: admins, useFlag: useAdminFlag, count: adminCount } = useSubset(item => item.source.group === 'admin')
152
+
153
+ const {
154
+ items: selectedAdmins,
155
+ areAllOn: areAllAdminSelected,
156
+ toggleAll: toggleAllAdminSelection,
157
+ count: selectedAdminCount,
158
+ toggle: toggleAdminSelection,
159
+ } = useAdminFlag('selected')
160
+ ```
161
+
162
+ It's also possible to create a subset of subset:
163
+
164
+ ```typescript
165
+ const { items: users, useSubset } = useCollection(rawUsers, {
166
+ /* ... */
167
+ })
168
+
169
+ const { items: admins, useSubset: useAdminSubset } = useSubset(item => item.source.group === 'admin')
170
+
171
+ const { items: activeAdmins } = useAdminSubset(item => item.source.status === 'active')
172
+ ```
@@ -0,0 +1,74 @@
1
+ import type {
2
+ Collection,
3
+ CollectionConfigProperties,
4
+ CollectionItem,
5
+ FlagRegistry,
6
+ GuessItemId,
7
+ UseFlagReturn,
8
+ } from '@core/packages/collection/types.ts'
9
+ import { useArrayFilter, useArrayMap } from '@vueuse/core'
10
+ import { computed, type ComputedRef } from 'vue'
11
+
12
+ export function createCollection<TSource, TFlag extends string, TProperties extends CollectionConfigProperties>(
13
+ items: ComputedRef<CollectionItem<TSource, TFlag, TProperties>[]>,
14
+ flagRegistry: FlagRegistry<TFlag>
15
+ ): Collection<TSource, TFlag, TProperties> {
16
+ function useFlag(flag: TFlag): UseFlagReturn<TSource, TFlag, TProperties> {
17
+ flagRegistry.assertFlag(flag)
18
+
19
+ const flaggedItems = useArrayFilter(items, item => item.flags[flag])
20
+
21
+ const ids = useArrayMap(flaggedItems, item => item.id)
22
+
23
+ const count = computed(() => flaggedItems.value.length)
24
+
25
+ const areAllOn = computed(() => items.value.length === count.value)
26
+
27
+ const areSomeOn = computed(() => count.value > 0)
28
+
29
+ const areNoneOn = computed(() => count.value === 0)
30
+
31
+ function toggle(id: GuessItemId<TSource, TProperties>, forcedValue?: boolean) {
32
+ flagRegistry.toggleFlag(id, flag, forcedValue)
33
+ }
34
+
35
+ function toggleAll(forcedValue = !areAllOn.value) {
36
+ for (const item of items.value) {
37
+ flagRegistry.toggleFlag(item.id, flag, forcedValue)
38
+ }
39
+ }
40
+
41
+ function useSubset(
42
+ filter: (item: CollectionItem<TSource, TFlag, TProperties>) => boolean
43
+ ): Collection<TSource, TFlag, TProperties> {
44
+ return createCollection(useArrayFilter(flaggedItems, filter), flagRegistry)
45
+ }
46
+
47
+ return {
48
+ items: flaggedItems,
49
+ ids,
50
+ count,
51
+ areAllOn,
52
+ areSomeOn,
53
+ areNoneOn,
54
+ toggle,
55
+ toggleAll,
56
+ useSubset,
57
+ }
58
+ }
59
+
60
+ function useSubset(
61
+ filter: (item: CollectionItem<TSource, TFlag, TProperties>) => boolean
62
+ ): Collection<TSource, TFlag, TProperties> {
63
+ return createCollection(useArrayFilter(items, filter), flagRegistry)
64
+ }
65
+
66
+ const count = computed(() => items.value.length)
67
+
68
+ return {
69
+ items,
70
+ count,
71
+ useFlag,
72
+ useSubset,
73
+ }
74
+ }
@@ -0,0 +1,39 @@
1
+ import { guessItemId } from '@core/packages/collection/guess-item-id.ts'
2
+ import type { CollectionConfigProperties, CollectionItem, FlagRegistry } from '@core/packages/collection/types.ts'
3
+ import { reactive } from 'vue'
4
+
5
+ export function createItem<TSource, TFlag extends string, TProperties extends CollectionConfigProperties>(
6
+ source: TSource,
7
+ getProperties: undefined | ((source: TSource) => TProperties),
8
+ flagRegistry: FlagRegistry<TFlag>
9
+ ): CollectionItem<TSource, TFlag, TProperties> {
10
+ const properties = reactive(getProperties?.(source) ?? ({} as TProperties))
11
+
12
+ const id = guessItemId(source, properties)
13
+
14
+ return {
15
+ id,
16
+ source,
17
+ toggleFlag(flag: TFlag, forcedValue?: boolean) {
18
+ flagRegistry.toggleFlag(id, flag, forcedValue)
19
+ },
20
+ flags: new Proxy({} as Record<TFlag, boolean>, {
21
+ has(_, flag: TFlag) {
22
+ return flagRegistry.isFlagDefined(flag)
23
+ },
24
+ get(_, flag: TFlag) {
25
+ if (!flagRegistry.isFlagDefined(flag)) {
26
+ return undefined
27
+ }
28
+
29
+ return flagRegistry.isFlagged(id, flag)
30
+ },
31
+ set(_, flag: TFlag, value: boolean) {
32
+ flagRegistry.toggleFlag(id, flag, value)
33
+
34
+ return true
35
+ },
36
+ }),
37
+ properties,
38
+ }
39
+ }
@@ -0,0 +1,26 @@
1
+ import type { CollectionConfigProperties, GuessItemId } from '@core/packages/collection/types.ts'
2
+
3
+ function assertValidId(id: unknown): asserts id is PropertyKey {
4
+ const type = typeof id
5
+
6
+ if (!['string', 'number', 'symbol'].includes(type)) {
7
+ throw new TypeError(`Invalid ID type: ${type}. Expected string, number, or bigint.`)
8
+ }
9
+ }
10
+
11
+ export function guessItemId<TSource, TProperties extends CollectionConfigProperties>(
12
+ source: TSource,
13
+ properties: TProperties | undefined
14
+ ) {
15
+ let id
16
+
17
+ if (typeof properties === 'object' && properties !== null && 'id' in properties) {
18
+ id = properties.id
19
+ } else if (typeof source === 'object' && source !== null && 'id' in source) {
20
+ id = source.id
21
+ }
22
+
23
+ assertValidId(id)
24
+
25
+ return id as GuessItemId<TSource, TProperties>
26
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types.ts'
2
+ export * from './use-collection.ts'