@xen-orchestra/web-core 0.31.1 → 0.33.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.
- package/lib/assets/css/_colors.pcss +8 -0
- package/lib/components/button-group/VtsButtonGroup.vue +5 -1
- package/lib/components/menu/MenuList.vue +1 -2
- package/lib/components/menu/MenuTrigger.vue +5 -11
- package/lib/components/modal/VtsModal.vue +82 -0
- package/lib/components/modal/VtsModalButton.vue +36 -0
- package/lib/components/modal/VtsModalCancelButton.vue +37 -0
- package/lib/components/modal/VtsModalConfirmButton.vue +21 -0
- package/lib/components/modal/VtsModalList.vue +34 -0
- package/lib/components/object-icon/VtsObjectIcon.vue +3 -8
- package/lib/components/status/VtsStatus.vue +66 -0
- package/lib/components/task/VtsQuickTaskList.vue +17 -5
- package/lib/components/tree/VtsTreeItem.vue +2 -2
- package/lib/components/ui/breadcrumb/UiBreadcrumb.vue +79 -0
- package/lib/components/ui/button/UiButton.vue +13 -67
- package/lib/components/ui/modal/UiModal.vue +164 -0
- package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +2 -2
- package/lib/composables/context.composable.ts +3 -5
- package/lib/composables/link-component.composable.ts +3 -2
- package/lib/composables/pagination.composable.ts +3 -2
- package/lib/composables/tree-filter.composable.ts +5 -3
- package/lib/icons/fa-icons.ts +13 -1
- package/lib/icons/index.ts +17 -0
- package/lib/locales/cs.json +60 -2
- package/lib/locales/de.json +40 -2
- package/lib/locales/en.json +27 -1
- package/lib/locales/es.json +51 -5
- package/lib/locales/fa.json +10 -10
- package/lib/locales/fr.json +28 -2
- package/lib/locales/it.json +4 -0
- package/lib/locales/nl.json +64 -14
- package/lib/locales/pt_BR.json +3 -3
- package/lib/locales/ru.json +41 -2
- package/lib/locales/sv.json +55 -1
- package/lib/locales/uk.json +4 -4
- package/lib/packages/collection/use-collection.ts +3 -2
- package/lib/packages/form-select/use-form-option-controller.ts +3 -2
- package/lib/packages/form-select/use-form-select.ts +8 -7
- package/lib/packages/menu/action.ts +4 -3
- package/lib/packages/menu/link.ts +5 -4
- package/lib/packages/menu/router-link.ts +3 -2
- package/lib/packages/menu/toggle-target.ts +3 -2
- package/lib/packages/modal/ModalProvider.vue +17 -0
- package/lib/packages/modal/README.md +253 -0
- package/lib/packages/modal/create-modal-opener.ts +103 -0
- package/lib/packages/modal/modal.store.ts +22 -0
- package/lib/packages/modal/types.ts +92 -0
- package/lib/packages/modal/use-modal.ts +53 -0
- package/lib/packages/progress/use-progress.ts +4 -3
- package/lib/packages/table/README.md +336 -0
- package/lib/packages/table/apply-extensions.ts +26 -0
- package/lib/packages/table/define-columns.ts +62 -0
- package/lib/packages/table/define-renderer/define-table-cell-renderer.ts +27 -0
- package/lib/packages/table/define-renderer/define-table-renderer.ts +47 -0
- package/lib/packages/table/define-renderer/define-table-row-renderer.ts +29 -0
- package/lib/packages/table/define-renderer/define-table-section-renderer.ts +29 -0
- package/lib/packages/table/define-table/define-multi-source-table.ts +39 -0
- package/lib/packages/table/define-table/define-table.ts +13 -0
- package/lib/packages/table/define-table/define-typed-table.ts +18 -0
- package/lib/packages/table/index.ts +11 -0
- package/lib/packages/table/transform-sources.ts +13 -0
- package/lib/packages/table/types/extensions.ts +16 -0
- package/lib/packages/table/types/index.ts +47 -0
- package/lib/packages/table/types/table-cell.ts +18 -0
- package/lib/packages/table/types/table-row.ts +20 -0
- package/lib/packages/table/types/table-section.ts +19 -0
- package/lib/packages/table/types/table.ts +28 -0
- package/lib/packages/threshold/use-threshold.ts +4 -3
- package/lib/types/vue-virtual-scroller.d.ts +101 -0
- package/lib/utils/injection-keys.util.ts +3 -0
- package/lib/utils/progress.util.ts +2 -1
- package/lib/utils/to-computed.util.ts +15 -0
- package/package.json +3 -2
- package/lib/components/backup-state/VtsBackupState.vue +0 -37
- package/lib/components/connection-status/VtsConnectionStatus.vue +0 -36
package/lib/locales/sv.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
+
"2nd-last": "2:a från slutet",
|
|
3
|
+
"3rd-last": "3:e från slutet",
|
|
2
4
|
"about": "Om",
|
|
5
|
+
"accept-self-signed-certificates": "Acceptera självsignerade certifikat",
|
|
3
6
|
"access-forum": "Gå till forum",
|
|
4
7
|
"access-xoa": "Anslut till XOA",
|
|
5
8
|
"account-organization-more": "Konto, organisation och mer…",
|
|
@@ -22,8 +25,11 @@
|
|
|
22
25
|
"alarm-type.sr_io_throughput_total_per_host": "SR IO genomströmning totalt per host överskrider @:n-percent",
|
|
23
26
|
"alarm-type.unknown": "Okänd larm-typ",
|
|
24
27
|
"alarms": "Larm",
|
|
28
|
+
"all-done": "Allt klart!",
|
|
25
29
|
"all-good": "Allt är bra!",
|
|
30
|
+
"all-quiet-launchpad": "Allt tyst på launchpad:en",
|
|
26
31
|
"allow-self-signed-ssl": "Du kan behöva tillåta själv-signerade SSL-certifikat i din webbläsare",
|
|
32
|
+
"api-error-details": "API fel-detaljer",
|
|
27
33
|
"appearance": "Utseende",
|
|
28
34
|
"ascending": "stigande",
|
|
29
35
|
"auto-generated": "Automatiskt generad",
|
|
@@ -32,9 +38,24 @@
|
|
|
32
38
|
"available-properties-for-advanced-filter": "Tillgängliga egenskaper för avancerade filter:",
|
|
33
39
|
"back-pool-dashboard": "Gå tillbaka till din Pool-översikt",
|
|
34
40
|
"back-site-dashboard": "Gå tillbaka till din översikt",
|
|
41
|
+
"backed-up-pools": "Säkerhetskopierade Pooler",
|
|
42
|
+
"backed-up-vms": "Säkerhetskopierade VM:ar",
|
|
35
43
|
"backup": "Säkerhetskopiera",
|
|
36
44
|
"backup-issues": "Säkerhetskopieringsproblem",
|
|
45
|
+
"backup-jobs": "Säkerhetskopieringsjobb",
|
|
46
|
+
"backup-network": "Säkerhetskopieringsnätverk",
|
|
47
|
+
"backup-repositories": "Säkerhetskopieringsförvaring",
|
|
37
48
|
"backup-repository": "Säkerhetskopieringsförvaring (lokalt, NFS, SMB)",
|
|
49
|
+
"backup-targets": "Säkerhetskopieringsmål",
|
|
50
|
+
"backup.continuous-replication": "Kontinuerlig replikering",
|
|
51
|
+
"backup.disaster-recovery": "Katastrofåterställning",
|
|
52
|
+
"backup.full": "Fullständig säkerhetskopiering",
|
|
53
|
+
"backup.incremental": "Inkrementell säkerhetskopiering",
|
|
54
|
+
"backup.metadata": "Säkerhetskopiering av metadata",
|
|
55
|
+
"backup.mirror": "Speglad säkerhetskopiering",
|
|
56
|
+
"backup.pool-metadata": "Pool-metadata",
|
|
57
|
+
"backup.rolling-snapshot": "Rullande ögonblicksbild",
|
|
58
|
+
"backup.xo-config": "XO-konfig",
|
|
38
59
|
"backups": "Säkerhetskopieringar",
|
|
39
60
|
"backups.jobs": "Jobb",
|
|
40
61
|
"backups.jobs.at-least-one-skipped": "Minst en hoppades över",
|
|
@@ -62,19 +83,28 @@
|
|
|
62
83
|
"bytes.ki": "KiB",
|
|
63
84
|
"bytes.mi": "MiB",
|
|
64
85
|
"cancel": "Avbryt",
|
|
86
|
+
"cbt-destroy-snapshot-data": "Rensa ögonblicksdata när CBT används",
|
|
65
87
|
"change-state": "Ändra",
|
|
66
88
|
"check-errors": "Kontrollera felen:",
|
|
67
89
|
"check-summing": "TX checksumma",
|
|
90
|
+
"checkpoint-snapshot": "Kontrollpunkt för ögonblicksbild",
|
|
68
91
|
"click-to-display-alarms": "Klicka för att visa larm:",
|
|
69
92
|
"click-to-return-default-pool": "Klicka här för att återgå till standardpoolen",
|
|
70
93
|
"close": "Stäng",
|
|
71
94
|
"coming-soon": "Kommer snart!",
|
|
72
95
|
"community": "Community",
|
|
73
96
|
"community-name": "{name} community",
|
|
97
|
+
"compression": "Komprimering",
|
|
98
|
+
"concurrency": "Samtidighet",
|
|
74
99
|
"configuration": "Konfiguration",
|
|
100
|
+
"configure-in-xo-5": "Konfigurera i XO 5",
|
|
75
101
|
"confirm-cancel": "Är du säker på att du vill avbryta?",
|
|
76
102
|
"confirm-delete": "Du kommer att radera {0}",
|
|
103
|
+
"connect": "Anslut",
|
|
104
|
+
"connect-another-pool": "Anslut annan pool",
|
|
105
|
+
"connect-pool": "Anslut pool",
|
|
77
106
|
"connected": "Ansluten",
|
|
107
|
+
"connected-to-ip": "Anslut till {ip}",
|
|
78
108
|
"connecting": "Ansluter",
|
|
79
109
|
"connection": "Anslutning",
|
|
80
110
|
"connection-failed": "Anslutning misslyckades",
|
|
@@ -82,7 +112,9 @@
|
|
|
82
112
|
"console": "Konsol",
|
|
83
113
|
"console-actions": "Konsolverktyg",
|
|
84
114
|
"console-clipboard": "Konsolurklipp",
|
|
115
|
+
"console-offline": "Konsoll nedstängd",
|
|
85
116
|
"console-unavailable": "Konsolen är inte tillgänglig",
|
|
117
|
+
"console-unavailable-reason": "Konsollen är inte längre tillgänglig då {type} är avstängd.",
|
|
86
118
|
"control-domain-memory": "Kontrolldomänens minne",
|
|
87
119
|
"copy": "Kopiera",
|
|
88
120
|
"copy-all": "Kopiera allt",
|
|
@@ -98,6 +130,7 @@
|
|
|
98
130
|
"core.group": "Grupp",
|
|
99
131
|
"core.hide": "Göm",
|
|
100
132
|
"core.open": "Öppna",
|
|
133
|
+
"core.open-in-new-tab": "Öppna i ny tabb",
|
|
101
134
|
"core.pagination.all": "Alla",
|
|
102
135
|
"core.pagination.show-by": "Sortera efter",
|
|
103
136
|
"core.query-search-bar.label": "Sökmotor",
|
|
@@ -127,14 +160,20 @@
|
|
|
127
160
|
"cpu-usage": "CPU-nyttjande",
|
|
128
161
|
"cpu-weight": "CPU-vikt",
|
|
129
162
|
"cpus": "CPU:er",
|
|
163
|
+
"crash-dump-storage-repository": "Lagringsplats för crashloggar",
|
|
130
164
|
"create": "Skapa",
|
|
165
|
+
"created-by": "Skapad av",
|
|
166
|
+
"created-on": "Skapad",
|
|
167
|
+
"cron-pattern": "Cron-mönster",
|
|
131
168
|
"custom-config": "Skräddarsydd konfiguration",
|
|
132
169
|
"dark-mode.auto": "Automatiskt mörkt läge",
|
|
133
170
|
"dark-mode.disable": "Inaktivera mörkt läge",
|
|
134
171
|
"dark-mode.enable": "Aktivera mörkt läge",
|
|
135
172
|
"dashboard": "Kontrollpanel",
|
|
173
|
+
"date": "Datum",
|
|
136
174
|
"default-behavior": "Standardbeteende",
|
|
137
175
|
"default-locking-mode": "Standard låsläge",
|
|
176
|
+
"default-storage-repository": "Standard lagringsplats",
|
|
138
177
|
"delete": "Radera",
|
|
139
178
|
"delete-vms": "Radera 1 VM | Radera {n} VMar",
|
|
140
179
|
"deploy": "Driftsätt",
|
|
@@ -164,10 +203,12 @@
|
|
|
164
203
|
"edit-config": "Ändra konfiguration",
|
|
165
204
|
"enabled": "Aktiverad",
|
|
166
205
|
"end-of-life": "Slutdatum",
|
|
206
|
+
"engines-off": "Motor avslagen, satellitbana stabil",
|
|
167
207
|
"eol": "EOL",
|
|
168
208
|
"error": "Fel",
|
|
169
209
|
"error-no-data": "Fel, kan inte samla data.",
|
|
170
210
|
"error-occurred": "Ett fel har uppstått",
|
|
211
|
+
"excluded-vms-tags": "Exkluderade VM-taggar",
|
|
171
212
|
"exit-fullscreen": "Stäng fullskärm",
|
|
172
213
|
"expiration-date": "Utgångsdatum",
|
|
173
214
|
"export": "Exportera",
|
|
@@ -177,6 +218,7 @@
|
|
|
177
218
|
"export-vm": "Exportera VM",
|
|
178
219
|
"export-vms": "Exportera VMar",
|
|
179
220
|
"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.)",
|
|
221
|
+
"failure": "Misslyckande",
|
|
180
222
|
"fast-clone": "Snabb-kloning",
|
|
181
223
|
"fetching-fresh-data": "Hämtar nya data",
|
|
182
224
|
"filter.comparison.contains": "Innehåller",
|
|
@@ -206,14 +248,20 @@
|
|
|
206
248
|
"go-back": "Gå tillbaka",
|
|
207
249
|
"gpus": "GPU:er",
|
|
208
250
|
"graphics-display": "Grafik & Skärm",
|
|
251
|
+
"guest-tools": "Gästverktyg",
|
|
209
252
|
"gzip": "gzip",
|
|
210
253
|
"hardware": "Hårdvara",
|
|
211
254
|
"hardware-specifications": "Hårdvaruspecifikationer",
|
|
255
|
+
"heartbeat-storage-repository": "Lagringsplats för hjärtslag",
|
|
212
256
|
"here": "Här",
|
|
257
|
+
"hide-successful-items": "Göm lyckade objekt i misslyckanderapporter",
|
|
213
258
|
"high-availability": "Hög tillgänglighet (HA)",
|
|
214
259
|
"host": "Host",
|
|
260
|
+
"host-currently-shutdown": "Denna host är i nuläget nedstängd, data kan inte hämtas.",
|
|
215
261
|
"host-description": "Host-beskrivning",
|
|
216
262
|
"host-internal-networks": "Host-interna nätverk",
|
|
263
|
+
"host-not-running": "Host:en kör inte",
|
|
264
|
+
"host-off": "Hosten är avstängd",
|
|
217
265
|
"host-status.halted": "Stoppad",
|
|
218
266
|
"host-status.running": "Kör",
|
|
219
267
|
"host-status.unknown": "Okänt",
|
|
@@ -229,6 +277,7 @@
|
|
|
229
277
|
"hyper-threading": "Multitrådning (SMT)",
|
|
230
278
|
"id": "ID",
|
|
231
279
|
"in-last-three-jobs": "I de senaste tre jobben",
|
|
280
|
+
"in-progress": "Pågående",
|
|
232
281
|
"install-settings": "Installationsinställningar",
|
|
233
282
|
"interfaces": "Gränssnitt | Gränssnitt | Gränssnitten",
|
|
234
283
|
"interrupted": "Avbruten",
|
|
@@ -236,9 +285,11 @@
|
|
|
236
285
|
"ip-address": "IP-adress",
|
|
237
286
|
"ip-addresses": "IP-adresser",
|
|
238
287
|
"ip-mode": "IP-läge",
|
|
288
|
+
"ip-port-placeholder": "adress[:port]",
|
|
239
289
|
"is-primary-host": "{name} är den primära hosten",
|
|
240
290
|
"iscsi-iqn": "iSCSI IQN",
|
|
241
291
|
"iso-dvd": "ISO/DVD",
|
|
292
|
+
"job-name": "Jobbnamn",
|
|
242
293
|
"job.vm-copy.bad-power-state": "VMen måste vara stoppad",
|
|
243
294
|
"job.vm-copy.in-progress": "Kopiering pågår…",
|
|
244
295
|
"job.vm-copy.missing-vms": "Ingen VM att kopiera",
|
|
@@ -286,6 +337,9 @@
|
|
|
286
337
|
"keep-me-logged": "Håll mig inloggad",
|
|
287
338
|
"keep-page-open": "Uppdatera inte och stäng inte tabben innan driftsättningen är klar.",
|
|
288
339
|
"language": "Språk",
|
|
340
|
+
"last": "Senaste",
|
|
341
|
+
"last-n-runs": "Senaste körning | Senaste {n} körningar",
|
|
342
|
+
"last-run-number": "Senaste körning #{n}",
|
|
289
343
|
"last-week": "Föregående vecka",
|
|
290
344
|
"learn-more": "Läs mer",
|
|
291
345
|
"license-socket": "Licensiera sockel",
|
|
@@ -293,8 +347,8 @@
|
|
|
293
347
|
"licensing": "Licensiering",
|
|
294
348
|
"load-average": "Genomsnittslast",
|
|
295
349
|
"load-now": "Ladda nu",
|
|
296
|
-
"loading-hosts": "Ladda hostar…",
|
|
297
350
|
"loading": "Laddning pågår…",
|
|
351
|
+
"loading-hosts": "Ladda hostar…",
|
|
298
352
|
"locking-mode": "Låsläge",
|
|
299
353
|
"locking-mode-default": "Standard låsläge",
|
|
300
354
|
"log-out": "Logga ut",
|
package/lib/locales/uk.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"3rd-last": "3-й останній",
|
|
4
4
|
"about": "Про нас",
|
|
5
5
|
"accept-self-signed-certificates": "Приймати самопідписані сертифікати",
|
|
6
|
-
"access-forum": "
|
|
6
|
+
"access-forum": "Доступ до форуму",
|
|
7
7
|
"access-xoa": "Доступ до XOA",
|
|
8
|
-
"account-organization-more": "Обліковий запис, організація та
|
|
8
|
+
"account-organization-more": "Обліковий запис, організація та інше…",
|
|
9
9
|
"add": "Додати",
|
|
10
10
|
"add-filter": "Додати фільтр",
|
|
11
11
|
"add-or": "+АБО",
|
|
@@ -201,11 +201,11 @@
|
|
|
201
201
|
"expiration-date": "Термін придатності",
|
|
202
202
|
"export": "Експорт",
|
|
203
203
|
"export-n-vms": "Експортовано 1 VM | Експортовано {n} VMs",
|
|
204
|
-
"export-n-vms-manually": "Експорт однієї ВМ вручну |
|
|
204
|
+
"export-n-vms-manually": "Експорт однієї ВМ вручну | Експорт {n} ВМ вручну",
|
|
205
205
|
"export-table-to": "Експорт таблиці в {type}",
|
|
206
206
|
"export-vm": "Експорт VM",
|
|
207
207
|
"export-vms": "Експорт VMs",
|
|
208
|
-
"export-vms-manually-information": "Експорт деяких ВМ не вдалося запустити автоматично, ймовірно, через ваші налаштування браузера. Для експорту натисніть на кожну ВМ. (Або скопіюйте
|
|
208
|
+
"export-vms-manually-information": "Експорт деяких ВМ не вдалося запустити автоматично, ймовірно, через ваші налаштування браузера. Для експорту натисніть на кожну ВМ. (Або скопіюйте посилання.)",
|
|
209
209
|
"failure": "Критична помилка",
|
|
210
210
|
"fast-clone": "Швидке клонування",
|
|
211
211
|
"fetching-fresh-data": "Отримання свіжих даних",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { guessItemId } from '@core/packages/collection/guess-item-id.ts'
|
|
2
2
|
import type { EmptyObject } from '@core/types/utility.type.ts'
|
|
3
|
+
import { toComputed } from '@core/utils/to-computed.util.ts'
|
|
3
4
|
import type {
|
|
4
5
|
Collection,
|
|
5
6
|
CollectionConfigFlags,
|
|
@@ -8,7 +9,7 @@ import type {
|
|
|
8
9
|
ExtractSourceId,
|
|
9
10
|
GetItemId,
|
|
10
11
|
} from './types.ts'
|
|
11
|
-
import { computed, type MaybeRefOrGetter
|
|
12
|
+
import { computed, type MaybeRefOrGetter } from 'vue'
|
|
12
13
|
import { createCollection } from './create-collection.ts'
|
|
13
14
|
import { createItem } from './create-item.ts'
|
|
14
15
|
import { useFlagRegistry } from './use-flag-registry.ts'
|
|
@@ -82,7 +83,7 @@ export function useCollection<
|
|
|
82
83
|
): Collection<TSource, TFlag, TProperties, $TId> {
|
|
83
84
|
const flagRegistry = useFlagRegistry<TFlag, $TId>(config?.flags)
|
|
84
85
|
|
|
85
|
-
const sources =
|
|
86
|
+
const sources = toComputed(_sources)
|
|
86
87
|
|
|
87
88
|
const items = computed(() =>
|
|
88
89
|
sources.value.map(source => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { toComputed } from '@core/utils/to-computed.util.ts'
|
|
1
2
|
import { unrefElement, useEventListener } from '@vueuse/core'
|
|
2
|
-
import {
|
|
3
|
+
import { inject, type MaybeRefOrGetter, ref, watchEffect } from 'vue'
|
|
3
4
|
import { type FormOption, IK_FORM_SELECT_CONTROLLER } from './types.ts'
|
|
4
5
|
|
|
5
6
|
export function useFormOptionController<TOption extends FormOption>(_option: MaybeRefOrGetter<TOption>) {
|
|
@@ -9,7 +10,7 @@ export function useFormOptionController<TOption extends FormOption>(_option: May
|
|
|
9
10
|
throw new Error('useFormOption needs a FormSelectController to be injected')
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const option =
|
|
13
|
+
const option = toComputed(_option)
|
|
13
14
|
|
|
14
15
|
const elementRef = ref<HTMLDivElement>()
|
|
15
16
|
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '@core/packages/collection'
|
|
8
8
|
import type { EmptyObject, MaybeArray } from '@core/types/utility.type.ts'
|
|
9
9
|
import { toArray } from '@core/utils/to-array.utils.ts'
|
|
10
|
+
import { toComputed } from '@core/utils/to-computed.util.ts'
|
|
10
11
|
import { computed, type ComputedRef, type MaybeRefOrGetter, provide, ref, type Ref, toRaw, toValue, watch } from 'vue'
|
|
11
12
|
import { guessLabel } from './guess-label.ts'
|
|
12
13
|
import { guessValue } from './guess-value.ts'
|
|
@@ -259,19 +260,19 @@ export function useFormSelect<
|
|
|
259
260
|
|
|
260
261
|
const normalizedSearchTerm = computed(() => normalizeSearchTerm(searchTerm))
|
|
261
262
|
|
|
262
|
-
const isMultiple =
|
|
263
|
+
const isMultiple = toComputed(config?.multiple, false) as ComputedRef<TMultiple>
|
|
263
264
|
|
|
264
|
-
const isDisabled =
|
|
265
|
+
const isDisabled = toComputed(config?.disabled, false)
|
|
265
266
|
|
|
266
|
-
const isLoading =
|
|
267
|
+
const isLoading = toComputed(config?.loading, false)
|
|
267
268
|
|
|
268
|
-
const isRequired =
|
|
269
|
+
const isRequired = toComputed(config?.required, false)
|
|
269
270
|
|
|
270
|
-
const placeholder =
|
|
271
|
+
const placeholder = toComputed(config?.placeholder, '')
|
|
271
272
|
|
|
272
|
-
const searchPlaceholder =
|
|
273
|
+
const searchPlaceholder = toComputed(config?.searchPlaceholder, '')
|
|
273
274
|
|
|
274
|
-
const isSearchable =
|
|
275
|
+
const isSearchable = toComputed(config?.searchable, false)
|
|
275
276
|
|
|
276
277
|
const sources = computed(() =>
|
|
277
278
|
config?.emptyOption !== undefined ? [EMPTY_OPTION, ...toValue(baseSources)] : toValue(baseSources)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseItem, type Menu, type MenuLike, parseConfigHolder } from '@core/packages/menu'
|
|
2
|
-
import {
|
|
2
|
+
import { toComputed } from '@core/utils/to-computed.util'
|
|
3
|
+
import { computed, type MaybeRefOrGetter, reactive, ref } from 'vue'
|
|
3
4
|
|
|
4
5
|
export interface MenuActionConfig {
|
|
5
6
|
handler: () => any
|
|
@@ -33,7 +34,7 @@ export class MenuAction extends BaseItem {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
get busyConfig() {
|
|
36
|
-
return
|
|
37
|
+
return toComputed(this.config.busy, false)
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
get isBusy() {
|
|
@@ -45,7 +46,7 @@ export class MenuAction extends BaseItem {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
get disabledConfig() {
|
|
48
|
-
return
|
|
49
|
+
return toComputed(this.config.disabled, false)
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
get isDisabled() {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseItem, type Menu, type MenuLike, parseConfigHolder } from '@core/packages/menu'
|
|
2
|
-
import {
|
|
2
|
+
import { toComputed } from '@core/utils/to-computed.util'
|
|
3
|
+
import { type MaybeRefOrGetter, reactive } from 'vue'
|
|
3
4
|
|
|
4
5
|
export interface MenuLinkConfig {
|
|
5
6
|
href: MaybeRefOrGetter<string>
|
|
@@ -34,9 +35,9 @@ export class MenuLink extends BaseItem {
|
|
|
34
35
|
as: 'a',
|
|
35
36
|
onMouseenter: () => this.activate(),
|
|
36
37
|
onClick: () => this.deactivate(),
|
|
37
|
-
href:
|
|
38
|
-
rel:
|
|
39
|
-
target:
|
|
38
|
+
href: toComputed(this.config.href),
|
|
39
|
+
rel: toComputed(this.config.rel, 'noreferrer noopener'),
|
|
40
|
+
target: toComputed(this.config.target, '_blank'),
|
|
40
41
|
'data-menu-id': this.menu.context.id,
|
|
41
42
|
})
|
|
42
43
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseItem, type Menu, type MenuLike, parseConfigHolder } from '@core/packages/menu'
|
|
2
|
-
import {
|
|
2
|
+
import { toComputed } from '@core/utils/to-computed.util'
|
|
3
|
+
import { markRaw, type MaybeRefOrGetter, reactive } from 'vue'
|
|
3
4
|
import { type RouteLocationRaw, RouterLink } from 'vue-router'
|
|
4
5
|
|
|
5
6
|
export interface MenuRouterLinkConfig {
|
|
@@ -31,7 +32,7 @@ export class MenuRouterLink extends BaseItem {
|
|
|
31
32
|
as: markRaw(RouterLink),
|
|
32
33
|
onMouseenter: () => this.activate(),
|
|
33
34
|
onClick: () => this.deactivate(),
|
|
34
|
-
to:
|
|
35
|
+
to: toComputed(this.config.to),
|
|
35
36
|
'data-menu-id': this.menu.context.id,
|
|
36
37
|
})
|
|
37
38
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { MenuToggleTrigger } from '@core/packages/menu'
|
|
2
|
+
import { toComputed } from '@core/utils/to-computed.util'
|
|
2
3
|
import { autoUpdate, flip, type Placement, shift, useFloating, type UseFloatingReturn } from '@floating-ui/vue'
|
|
3
4
|
import { unrefElement } from '@vueuse/core'
|
|
4
|
-
import { computed, reactive, ref, type Ref,
|
|
5
|
+
import { computed, reactive, ref, type Ref, type UnwrapRef } from 'vue'
|
|
5
6
|
|
|
6
7
|
export interface MenuToggleTargetConfig {
|
|
7
8
|
placement?: Placement
|
|
@@ -26,7 +27,7 @@ export class MenuToggleTarget {
|
|
|
26
27
|
public config: MenuToggleTargetConfig
|
|
27
28
|
) {
|
|
28
29
|
const { floatingStyles } = useFloating(trigger.element, this.element, {
|
|
29
|
-
placement:
|
|
30
|
+
placement: toComputed(config.placement, 'bottom-start'),
|
|
30
31
|
open: trigger.isOpen,
|
|
31
32
|
whileElementsMounted: autoUpdate,
|
|
32
33
|
middleware: [shift(), flip()],
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<slot />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" setup>
|
|
6
|
+
import { IK_MODAL, type RegisteredModal } from '@core/packages/modal/types.ts'
|
|
7
|
+
import { computed, provide } from 'vue'
|
|
8
|
+
|
|
9
|
+
const { modal } = defineProps<{
|
|
10
|
+
modal: RegisteredModal
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
provide(
|
|
14
|
+
IK_MODAL,
|
|
15
|
+
computed(() => modal)
|
|
16
|
+
)
|
|
17
|
+
</script>
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Modal System
|
|
2
|
+
|
|
3
|
+
### Modals list component
|
|
4
|
+
|
|
5
|
+
First, create a component which will display the modals.
|
|
6
|
+
|
|
7
|
+
If needed, you can use the `ModalProvider` component to provide the `modal` to children components.
|
|
8
|
+
|
|
9
|
+
For example:
|
|
10
|
+
|
|
11
|
+
```vue
|
|
12
|
+
<template>
|
|
13
|
+
<div v-if="modalStore.modals.length > 0" class="modals">
|
|
14
|
+
<ModalProvider v-for="modal of modalStore.modals" :key="modal.id" :modal>
|
|
15
|
+
<component :is="modal.component" class="modal" v-bind="modal.bindings" />
|
|
16
|
+
</ModalProvider>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script lang="ts" setup>
|
|
21
|
+
const modalStore = useModalStore()
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<style lang="postcss" scoped>
|
|
25
|
+
.modals {
|
|
26
|
+
position: fixed;
|
|
27
|
+
inset: 0;
|
|
28
|
+
background-color: var(--color-opacity-primary);
|
|
29
|
+
z-index: 1010;
|
|
30
|
+
|
|
31
|
+
.modal:not(:last-child) {
|
|
32
|
+
filter: brightness(0.8);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Modal component
|
|
39
|
+
|
|
40
|
+
You then need to create a Modal component which will emit `confirm` and `cancel` events, as needed.
|
|
41
|
+
|
|
42
|
+
```vue
|
|
43
|
+
<template>
|
|
44
|
+
<div class="modal-container">
|
|
45
|
+
<div class="modal">
|
|
46
|
+
<h1>My modal</h1>
|
|
47
|
+
<button @click="emit('confirm')">Confirm</button>
|
|
48
|
+
<button @click="emit('cancel')">Cancel</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script lang="ts" setup>
|
|
54
|
+
const emit = defineEmits<{
|
|
55
|
+
confirm: []
|
|
56
|
+
cancel: []
|
|
57
|
+
}>()
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<style lang="postcss" scoped>
|
|
61
|
+
.modal-container {
|
|
62
|
+
position: fixed;
|
|
63
|
+
inset: 0;
|
|
64
|
+
display: flex;
|
|
65
|
+
justify-content: center;
|
|
66
|
+
align-items: center;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.modal {
|
|
70
|
+
background: white;
|
|
71
|
+
border-radius: 8px;
|
|
72
|
+
padding: 2rem;
|
|
73
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Opening a modal (`useModal` composable)
|
|
79
|
+
|
|
80
|
+
You can create a function to open a modal with `useModal`
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const openModal = useModal(config)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| Property | Type | Required | Default | Description |
|
|
87
|
+
| --------------------- | ------------------------------------- | :------: | :---------: | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
88
|
+
| component | `Promise<Component>` | ✓ | | The promise of the modal component to open. (e.g: `component: import('path/to/modal.vue')` |
|
|
89
|
+
| props | `Record<string, MaybeRef<any>>` | | `{}` | The props to pass to the modal component |
|
|
90
|
+
| onConfirm | `(...args: any[]) => TConfirmPayload` | | `undefined` | An optional callback to call when the modal is confirmed. It will take the args of the `confirm` event of the modal's `defineEmits`, if any. |
|
|
91
|
+
| onCancel | `(...args: any[]) => TCancelPayload` | | `undefined` | An optional callback to call when the modal is cancelled. It will take the args of the `cancel` event of the modal's `defineEmits`, if any. |
|
|
92
|
+
| keepOpenOnRouteChange | `boolean` | | `false` | By default, the modal will close when the use navigates away from the page. If `true`, the modal will stay open until it is closed manually. |
|
|
93
|
+
|
|
94
|
+
The result of `openModal()` will be `Promise<ModalConfirmResponse<TConfirmPayload> | ModalCancelResponse<TCancelPayload>>`
|
|
95
|
+
|
|
96
|
+
If `onConfirm` / `onCancel` returns a `Promise`, then the modal will not close until the promise is resolved, and its `busy` state will be set to `true`.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const openMyModal = useModal({
|
|
100
|
+
component: import('path/to/MyModal.vue'),
|
|
101
|
+
props: {
|
|
102
|
+
foo: 'Foo',
|
|
103
|
+
bar: computed(() => someBar),
|
|
104
|
+
},
|
|
105
|
+
onConfirm: () => console.log('Confirmed!'),
|
|
106
|
+
onCancel: () => console.log('Cancelled!'),
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<button @click="openModal()">Open modal</button>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Modal payload
|
|
115
|
+
|
|
116
|
+
Modal payloads are fully typed.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const openMyModal = useModal({
|
|
120
|
+
component: import('path/to/MyModal.vue'),
|
|
121
|
+
onConfirm: () => 'Hello',
|
|
122
|
+
onCancel: () => 1234,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const result = await openMyModal()
|
|
126
|
+
|
|
127
|
+
if (result.confirmed) {
|
|
128
|
+
result.payload // string
|
|
129
|
+
} else {
|
|
130
|
+
result.payload // number
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Opening a modal with arguments
|
|
135
|
+
|
|
136
|
+
You can pass a function returning a config instead of a config object.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const openMyModal = useModal((name: string) => ({
|
|
140
|
+
component: import('path/to/MyModal.vue'),
|
|
141
|
+
props: { name },
|
|
142
|
+
onConfirm: () => console.log('Confirmed for', name),
|
|
143
|
+
onCancel: () => console.log('Cancelled for', name),
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<button @click="openMyModal('John')">Open John modal</button>
|
|
149
|
+
<button @click="openMyModal('Jane')">Open Jane modal</button>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## `onConfirm` and `onCancel` event args
|
|
153
|
+
|
|
154
|
+
If your Modal component defines args for `confirm` and `cancel` events, you'll get them as arguments of the `onConfirm` and `onCancel` callbacks.
|
|
155
|
+
|
|
156
|
+
```vue
|
|
157
|
+
<!-- MyModal.vue -->
|
|
158
|
+
<template>
|
|
159
|
+
<div class="modal-container">
|
|
160
|
+
<div class="modal">
|
|
161
|
+
<h1>My modal</h1>
|
|
162
|
+
<button @click="emit('confirm', 1)">Select 1</button>
|
|
163
|
+
<button @click="emit('confirm', 10)">Select 10</button>
|
|
164
|
+
<button @click="emit('cancel')">Cancel</button>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</template>
|
|
168
|
+
|
|
169
|
+
<script lang="ts" setup>
|
|
170
|
+
defineEmits<{
|
|
171
|
+
confirm: [count: number]
|
|
172
|
+
cancel: []
|
|
173
|
+
}>()
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const openModal = useModal({
|
|
178
|
+
component: import('path/to/MyModal.vue'),
|
|
179
|
+
onConfirm: (count: number) => console.log('Confirmed with count', count),
|
|
180
|
+
})
|
|
181
|
+
</script>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Opening a modal from elsewhere
|
|
185
|
+
|
|
186
|
+
`useModal` muse be called at the root of your component.
|
|
187
|
+
|
|
188
|
+
If you need to open a modal from elsewhere, you can use `useModal()` with no arguments to get a function that you can call later to open the modal.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const openModal = useModal()
|
|
192
|
+
|
|
193
|
+
function myHandler() {
|
|
194
|
+
return someApiManager.doSomething(myArg, openModal)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// api-manager.ts
|
|
198
|
+
|
|
199
|
+
function doSomething(arg: string, openModal: OpenModal) {
|
|
200
|
+
// ... do something
|
|
201
|
+
|
|
202
|
+
openModal('some-id', {
|
|
203
|
+
component: import('path/to/MyModal.vue'),
|
|
204
|
+
props: { arg },
|
|
205
|
+
onConfirm: () => console.log('Confirmed!'),
|
|
206
|
+
onCancel: () => console.log('Cancelled!'),
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Chaining modals
|
|
212
|
+
|
|
213
|
+
You can open a modal from another modal.
|
|
214
|
+
|
|
215
|
+
For example, you can open a confirmation modal ("Are you sure...") from the `onConfirm` of a delete modal, or from the `onCancel` of a form modal which has unsaved changes.
|
|
216
|
+
|
|
217
|
+
If the second modal is canceled, the first modal will stay open.
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const openModal = useModal()
|
|
221
|
+
|
|
222
|
+
const openDeleteModal = useModal({
|
|
223
|
+
component: import('path/to/DeleteModal.vue'),
|
|
224
|
+
onConfirm: () =>
|
|
225
|
+
openModal('confirm-delete', {
|
|
226
|
+
component: import('path/to/ConfirmModal.vue'),
|
|
227
|
+
props: { message: 'Are you sure?' },
|
|
228
|
+
onConfirm: async () => {
|
|
229
|
+
console.log('Deleting...')
|
|
230
|
+
await deleteResource()
|
|
231
|
+
},
|
|
232
|
+
}),
|
|
233
|
+
})
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Aborting the closing of a modal
|
|
237
|
+
|
|
238
|
+
If you want to prevent a modal from closing manually, you can return the `ABORT_MODAL` symbol from the `onConfirm` or `onCancel` handlers.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const openModal = useModal()
|
|
242
|
+
|
|
243
|
+
const openDeleteModal = useModal({
|
|
244
|
+
component: import('path/to/DeleteModal.vue'),
|
|
245
|
+
onConfirm: async () => {
|
|
246
|
+
try {
|
|
247
|
+
await tryingToDeleteResource()
|
|
248
|
+
} catch (e) {
|
|
249
|
+
return ABORT_MODAL
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
})
|
|
253
|
+
```
|