@xen-orchestra/web-core 0.31.0 → 0.32.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/backdrop/VtsBackdrop.vue +9 -1
- package/lib/components/button-group/VtsButtonGroup.vue +5 -1
- package/lib/components/menu/MenuList.vue +1 -0
- 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/VtsQuickTaskButton.vue +3 -2
- package/lib/components/task/VtsQuickTaskList.vue +17 -5
- package/lib/components/tree/VtsTreeItem.vue +2 -2
- package/lib/components/ui/button/UiButton.vue +13 -67
- package/lib/components/ui/input/UiInput.vue +4 -1
- 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 +4 -0
- package/lib/icons/index.ts +17 -0
- package/lib/locales/en.json +14 -1
- package/lib/locales/fr.json +14 -1
- 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
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useModalStore } from '@core/packages/modal/modal.store.ts'
|
|
2
|
+
import {
|
|
3
|
+
ABORT_MODAL,
|
|
4
|
+
ModalCancelResponse,
|
|
5
|
+
type ModalConfig,
|
|
6
|
+
ModalConfirmResponse,
|
|
7
|
+
type ModalHandlerArgs,
|
|
8
|
+
type ModalResponse,
|
|
9
|
+
} from '@core/packages/modal/types.ts'
|
|
10
|
+
import { computed, defineAsyncComponent, reactive, ref, watch } from 'vue'
|
|
11
|
+
import { useRoute } from 'vue-router'
|
|
12
|
+
|
|
13
|
+
export function createModalOpener() {
|
|
14
|
+
const modalStore = useModalStore()
|
|
15
|
+
|
|
16
|
+
const route = useRoute()
|
|
17
|
+
|
|
18
|
+
const ids = new Set<symbol | string>()
|
|
19
|
+
|
|
20
|
+
function closeById(id: string | symbol) {
|
|
21
|
+
modalStore.removeModal(id)
|
|
22
|
+
ids.delete(id)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
watch(
|
|
26
|
+
() => route.path,
|
|
27
|
+
() => {
|
|
28
|
+
ids.forEach(id => {
|
|
29
|
+
if (!modalStore.getModal(id)?.keepOpenOnRouteChange) {
|
|
30
|
+
closeById(id)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return function openModal<
|
|
37
|
+
TProps,
|
|
38
|
+
TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
|
|
39
|
+
TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
|
|
40
|
+
TConfirmPayload = undefined,
|
|
41
|
+
TCancelPayload = undefined,
|
|
42
|
+
>(id: string | symbol, config: ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>) {
|
|
43
|
+
const close = () => closeById(id)
|
|
44
|
+
|
|
45
|
+
const promise = new Promise<ModalResponse<TConfirmPayload, TCancelPayload>>(resolve => {
|
|
46
|
+
const isBusy = ref(false)
|
|
47
|
+
|
|
48
|
+
modalStore.addModal({
|
|
49
|
+
id,
|
|
50
|
+
component: defineAsyncComponent(() => config.component),
|
|
51
|
+
isBusy: computed(() => isBusy.value),
|
|
52
|
+
props: reactive(config.props ?? {}),
|
|
53
|
+
keepOpenOnRouteChange: config.keepOpenOnRouteChange ?? false,
|
|
54
|
+
onConfirm: async (...args: TConfirmArgs) => {
|
|
55
|
+
try {
|
|
56
|
+
isBusy.value = true
|
|
57
|
+
|
|
58
|
+
const result = config.onConfirm ? await config.onConfirm(...args) : (undefined as TConfirmPayload)
|
|
59
|
+
|
|
60
|
+
if (result === ABORT_MODAL || result instanceof ModalCancelResponse) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (result instanceof ModalConfirmResponse) {
|
|
65
|
+
resolve(result)
|
|
66
|
+
} else {
|
|
67
|
+
resolve(new ModalConfirmResponse(result))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
close()
|
|
71
|
+
} finally {
|
|
72
|
+
isBusy.value = false
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
onCancel: async (...args: TCancelArgs) => {
|
|
76
|
+
try {
|
|
77
|
+
isBusy.value = true
|
|
78
|
+
|
|
79
|
+
const result = config.onCancel ? await config.onCancel(...args) : (undefined as TCancelPayload)
|
|
80
|
+
|
|
81
|
+
if (result === ABORT_MODAL || result instanceof ModalCancelResponse) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (result instanceof ModalConfirmResponse) {
|
|
86
|
+
resolve(new ModalCancelResponse(result.payload))
|
|
87
|
+
} else {
|
|
88
|
+
resolve(new ModalCancelResponse(result))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
close()
|
|
92
|
+
} finally {
|
|
93
|
+
isBusy.value = false
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return Object.assign(promise, {
|
|
100
|
+
close,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RegisteredModal } from '@core/packages/modal/types.ts'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import { computed, shallowReactive } from 'vue'
|
|
4
|
+
|
|
5
|
+
export const useModalStore = defineStore('new-modal', () => {
|
|
6
|
+
const modals = shallowReactive(new Map<symbol | string, RegisteredModal>())
|
|
7
|
+
|
|
8
|
+
const addModal = (modal: RegisteredModal) => {
|
|
9
|
+
modals.set(modal.id, modal)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const removeModal = (id: symbol | string) => {
|
|
13
|
+
modals.delete(id)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
modals: computed(() => Array.from(modals.values())),
|
|
18
|
+
getModal: (id: symbol | string) => modals.get(id),
|
|
19
|
+
addModal,
|
|
20
|
+
removeModal,
|
|
21
|
+
}
|
|
22
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { MaybeRef } from '@vueuse/core'
|
|
2
|
+
import type { Component, ComputedRef, InjectionKey } from 'vue'
|
|
3
|
+
|
|
4
|
+
export type ModalPropsOption<TProps> = {
|
|
5
|
+
[K in keyof TProps]: MaybeRef<TProps[K]>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type MaybePromise<T> = T | Promise<T>
|
|
9
|
+
|
|
10
|
+
export type AreAllPropsOptional<TProps> = Record<string, never> extends TProps ? true : false
|
|
11
|
+
|
|
12
|
+
export type RegisteredModal = {
|
|
13
|
+
id: symbol | string
|
|
14
|
+
component: Component
|
|
15
|
+
keepOpenOnRouteChange: boolean
|
|
16
|
+
props: object
|
|
17
|
+
isBusy: ComputedRef<boolean>
|
|
18
|
+
onConfirm: (...args: any[]) => void | Promise<void>
|
|
19
|
+
onCancel: (...args: any[]) => void | Promise<void>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ModalHandlerArgs<TProps, THandler extends 'onConfirm' | 'onCancel'> = TProps extends {
|
|
23
|
+
[K in THandler]?: (...args: infer TArgs) => any
|
|
24
|
+
}
|
|
25
|
+
? TArgs
|
|
26
|
+
: never
|
|
27
|
+
|
|
28
|
+
export abstract class AbstractModalResponse<TPayload> {
|
|
29
|
+
constructor(public readonly payload: TPayload) {}
|
|
30
|
+
|
|
31
|
+
abstract get confirmed(): boolean
|
|
32
|
+
abstract get canceled(): boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class ModalConfirmResponse<TPayload> extends AbstractModalResponse<TPayload> {
|
|
36
|
+
public readonly confirmed = true
|
|
37
|
+
public readonly canceled = false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class ModalCancelResponse<TPayload> extends AbstractModalResponse<TPayload> {
|
|
41
|
+
public readonly confirmed = false
|
|
42
|
+
public readonly canceled = true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type ModalResponse<TConfirmPayload, TCancelPayload> =
|
|
46
|
+
| ModalConfirmResponse<TConfirmPayload>
|
|
47
|
+
| ModalCancelResponse<TCancelPayload>
|
|
48
|
+
|
|
49
|
+
export const ABORT_MODAL = Symbol('abort modal')
|
|
50
|
+
|
|
51
|
+
export type OpenModalReturn<TConfirmPayload, TCancelPayload> = Promise<
|
|
52
|
+
ModalResponse<TConfirmPayload, TCancelPayload>
|
|
53
|
+
> & {
|
|
54
|
+
close: () => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const IK_MODAL = Symbol('modal') as InjectionKey<ComputedRef<RegisteredModal>>
|
|
58
|
+
|
|
59
|
+
export type ModalPropsConfigEntry<TProps> =
|
|
60
|
+
AreAllPropsOptional<TProps> extends true ? { props?: ModalPropsOption<TProps> } : { props: ModalPropsOption<TProps> }
|
|
61
|
+
|
|
62
|
+
export type ModalConfig<
|
|
63
|
+
TProps,
|
|
64
|
+
TConfirmArgs extends any[],
|
|
65
|
+
TCancelArgs extends any[],
|
|
66
|
+
TConfirmPayload,
|
|
67
|
+
TCancelPayload,
|
|
68
|
+
> = {
|
|
69
|
+
component: Promise<{
|
|
70
|
+
default: abstract new () => {
|
|
71
|
+
$props: TProps
|
|
72
|
+
}
|
|
73
|
+
}>
|
|
74
|
+
onConfirm?: (...args: TConfirmArgs) => MaybePromise<ModalResponse<TConfirmPayload, any> | TConfirmPayload>
|
|
75
|
+
onCancel?: (...args: TCancelArgs) => MaybePromise<ModalResponse<TCancelPayload, any> | TCancelPayload>
|
|
76
|
+
keepOpenOnRouteChange?: boolean
|
|
77
|
+
} & ModalPropsConfigEntry<TProps>
|
|
78
|
+
|
|
79
|
+
export type UseModalReturn<TArgs extends any[] = any[], TConfirmPayload = any, TCancelPayload = any> = (
|
|
80
|
+
...args: TArgs
|
|
81
|
+
) => OpenModalReturn<TConfirmPayload, TCancelPayload>
|
|
82
|
+
|
|
83
|
+
export type OpenModal = <
|
|
84
|
+
TProps,
|
|
85
|
+
TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
|
|
86
|
+
TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
|
|
87
|
+
TConfirmPayload = undefined,
|
|
88
|
+
TCancelPayload = undefined,
|
|
89
|
+
>(
|
|
90
|
+
id: string,
|
|
91
|
+
config: ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
|
|
92
|
+
) => OpenModalReturn<TConfirmPayload, TCancelPayload>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createModalOpener } from '@core/packages/modal/create-modal-opener.ts'
|
|
2
|
+
import { type ModalConfig, type ModalHandlerArgs, type UseModalReturn } from '@core/packages/modal/types.ts'
|
|
3
|
+
|
|
4
|
+
export function useModal(): ReturnType<typeof createModalOpener>
|
|
5
|
+
|
|
6
|
+
export function useModal<
|
|
7
|
+
TProps,
|
|
8
|
+
TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
|
|
9
|
+
TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
|
|
10
|
+
TConfirmPayload = undefined,
|
|
11
|
+
TCancelPayload = undefined,
|
|
12
|
+
>(
|
|
13
|
+
config: ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
|
|
14
|
+
): UseModalReturn<[], TConfirmPayload, TCancelPayload>
|
|
15
|
+
|
|
16
|
+
export function useModal<
|
|
17
|
+
TConfigBuilderArgs extends any[],
|
|
18
|
+
TProps,
|
|
19
|
+
TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
|
|
20
|
+
TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
|
|
21
|
+
TConfirmPayload = undefined,
|
|
22
|
+
TCancelPayload = undefined,
|
|
23
|
+
>(
|
|
24
|
+
configBuilder: (
|
|
25
|
+
...args: TConfigBuilderArgs
|
|
26
|
+
) => ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
|
|
27
|
+
): UseModalReturn<TConfigBuilderArgs, TConfirmPayload, TCancelPayload>
|
|
28
|
+
|
|
29
|
+
export function useModal<
|
|
30
|
+
TConfigBuilderArgs extends any[],
|
|
31
|
+
TProps,
|
|
32
|
+
TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
|
|
33
|
+
TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
|
|
34
|
+
TConfig extends ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>,
|
|
35
|
+
TConfirmPayload = undefined,
|
|
36
|
+
TCancelPayload = undefined,
|
|
37
|
+
>(
|
|
38
|
+
configOrBuilder?: TConfig | ((...args: TConfigBuilderArgs) => TConfig)
|
|
39
|
+
): UseModalReturn<TConfigBuilderArgs, TConfirmPayload, TCancelPayload> | ReturnType<typeof createModalOpener> {
|
|
40
|
+
const openModal = createModalOpener()
|
|
41
|
+
|
|
42
|
+
if (configOrBuilder === undefined) {
|
|
43
|
+
return openModal
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const id = Symbol('modal')
|
|
47
|
+
|
|
48
|
+
return (...args: TConfigBuilderArgs) => {
|
|
49
|
+
const config = typeof configOrBuilder === 'function' ? configOrBuilder(...args) : configOrBuilder
|
|
50
|
+
|
|
51
|
+
return openModal(id, config)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { toComputed } from '@core/utils/to-computed.util.ts'
|
|
1
2
|
import type { Progress } from './types.ts'
|
|
2
|
-
import { computed, type MaybeRefOrGetter
|
|
3
|
+
import { computed, type MaybeRefOrGetter } from 'vue'
|
|
3
4
|
|
|
4
5
|
export function useProgress(rawCurrent: MaybeRefOrGetter<number>, rawTotal: MaybeRefOrGetter<number>): Progress {
|
|
5
|
-
const current =
|
|
6
|
+
const current = toComputed(rawCurrent)
|
|
6
7
|
|
|
7
|
-
const total =
|
|
8
|
+
const total = toComputed(rawTotal)
|
|
8
9
|
|
|
9
10
|
const percentage = computed(() => (total.value === 0 ? 0 : (current.value / total.value) * 100))
|
|
10
11
|
|