@xen-orchestra/web-core 0.53.0 → 0.55.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/components/button-group/VtsButtonGroup.vue +11 -1
- package/lib/components/drawer/VtsDrawer.vue +85 -0
- package/lib/components/drawer/VtsDrawerButton.vue +21 -0
- package/lib/components/drawer/VtsDrawerCancelButton.vue +33 -0
- package/lib/components/drawer/VtsDrawerConfirmButton.vue +30 -0
- package/lib/components/drawer/VtsDrawerList.vue +34 -0
- package/lib/components/input-wrapper/VtsInputWrapper.vue +2 -1
- package/lib/components/layout/VtsLayoutSidebar.vue +5 -2
- package/lib/components/modal/VtsModalButton.vue +0 -1
- package/lib/components/query-builder/VtsQueryBuilderModal.vue +0 -1
- package/lib/components/state-hero/VtsStateHero.vue +17 -7
- package/lib/components/table/cells/VtsDoubleLinkCell.vue +1 -1
- package/lib/components/table/cells/VtsHeaderCell.vue +1 -17
- package/lib/components/table/cells/VtsTagCell.vue +4 -2
- package/lib/components/ui/drawer/UiDrawer.vue +123 -0
- package/lib/components/ui/input/UiInput.vue +7 -3
- package/lib/components/ui/modal/UiModal.vue +10 -18
- package/lib/composables/default-tab.composable.ts +4 -0
- package/lib/icons/action-icons.ts +6 -2
- package/lib/icons/custom-icons.ts +16 -0
- package/lib/icons/object-icons.ts +33 -18
- package/lib/icons/status-icons.ts +1 -1
- package/lib/locales/cs.json +135 -4
- package/lib/locales/da.json +1 -3
- package/lib/locales/de.json +141 -26
- package/lib/locales/en.json +124 -10
- package/lib/locales/es.json +32 -5
- package/lib/locales/fr.json +124 -10
- package/lib/locales/ko.json +132 -0
- package/lib/locales/nb-NO.json +18 -20
- package/lib/locales/nl.json +111 -13
- package/lib/locales/pt-BR.json +0 -3
- package/lib/locales/pt.json +0 -3
- package/lib/locales/sk.json +134 -3
- package/lib/locales/sv.json +171 -5
- package/lib/locales/zh-Hans.json +705 -635
- package/lib/packages/drawer/DrawerProvider.vue +17 -0
- package/lib/packages/drawer/README.md +189 -0
- package/lib/packages/drawer/create-drawer-opener.ts +119 -0
- package/lib/packages/drawer/drawer.store.ts +22 -0
- package/lib/packages/drawer/types.ts +93 -0
- package/lib/packages/drawer/use-drawer.ts +53 -0
- package/lib/packages/form-validation/custom-rules/ipv4-or-cidr.rule.ts +17 -0
- package/lib/packages/form-validation/index.ts +1 -0
- package/lib/packages/modal/create-modal-opener.ts +12 -10
- package/lib/tables/column-definitions/action-column.ts +1 -1
- package/lib/tables/column-definitions/address-column.ts +1 -1
- package/lib/tables/column-definitions/button-column.ts +1 -1
- package/lib/tables/column-definitions/button-icon-column.ts +1 -1
- package/lib/tables/column-definitions/collapsed-list-column.ts +1 -1
- package/lib/tables/column-definitions/date-column.ts +1 -1
- package/lib/tables/column-definitions/double-link-column.ts +1 -1
- package/lib/tables/column-definitions/info-column.ts +1 -1
- package/lib/tables/column-definitions/input-column.ts +1 -5
- package/lib/tables/column-definitions/link-column.ts +1 -1
- package/lib/tables/column-definitions/link-or-text-column.ts +1 -1
- package/lib/tables/column-definitions/literal-column.ts +1 -1
- package/lib/tables/column-definitions/number-column.ts +1 -1
- package/lib/tables/column-definitions/percent-column.ts +1 -1
- package/lib/tables/column-definitions/progress-bar-column.ts +1 -1
- package/lib/tables/column-definitions/select-column.ts +1 -1
- package/lib/tables/column-definitions/status-column.ts +2 -2
- package/lib/tables/column-definitions/tag-column.ts +3 -2
- package/lib/tables/column-definitions/text-column.ts +1 -1
- package/lib/tables/column-definitions/truncated-text-column.ts +1 -1
- package/lib/tables/column-sets/backup-job-schedule-columns.ts +2 -2
- package/lib/tables/column-sets/backup-log-columns.ts +1 -1
- package/lib/tables/column-sets/new-vm-network-columns.ts +0 -2
- package/lib/tables/column-sets/new-vm-sr-columns.ts +0 -1
- package/lib/tables/column-sets/server-columns.ts +1 -1
- package/lib/tables/column-sets/snapshot-columns.ts +1 -1
- package/lib/tables/column-sets/sr-columns.ts +2 -2
- package/lib/tables/column-sets/traffic-rules-columns.ts +5 -7
- package/lib/tables/column-sets/vif-network-columns.ts +24 -0
- package/lib/tables/helpers/render-head-cell.ts +2 -4
- package/lib/tables/types.ts +0 -2
- package/lib/types/connection.ts +14 -0
- package/lib/types/storage-repository.type.ts +14 -0
- package/lib/utils/sr.utils.ts +34 -0
- package/package.json +4 -4
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<slot />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" setup>
|
|
6
|
+
import { IK_DRAWER, type RegisteredDrawer } from '@core/packages/drawer/types.ts'
|
|
7
|
+
import { computed, provide } from 'vue'
|
|
8
|
+
|
|
9
|
+
const { drawer } = defineProps<{
|
|
10
|
+
drawer: RegisteredDrawer
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
provide(
|
|
14
|
+
IK_DRAWER,
|
|
15
|
+
computed(() => drawer)
|
|
16
|
+
)
|
|
17
|
+
</script>
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Drawer System
|
|
2
|
+
|
|
3
|
+
### Drawers list component
|
|
4
|
+
|
|
5
|
+
Add `VtsDrawerList` once in your app layout. It renders all active drawers from the store.
|
|
6
|
+
|
|
7
|
+
```vue
|
|
8
|
+
<!-- App.vue -->
|
|
9
|
+
<template>
|
|
10
|
+
...
|
|
11
|
+
<VtsDrawerList />
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script lang="ts" setup>
|
|
15
|
+
import VtsDrawerList from '@core/components/drawer/VtsDrawerList.vue'
|
|
16
|
+
</script>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Opening a drawer (`useDrawer` composable)
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const openMyDrawer = useDrawer({
|
|
23
|
+
component: import('path/to/MyDrawer.vue'),
|
|
24
|
+
props: {
|
|
25
|
+
foo: 'Foo',
|
|
26
|
+
},
|
|
27
|
+
onConfirm: () => console.log('Confirmed!'),
|
|
28
|
+
onCancel: () => console.log('Cancelled!'),
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<button @click="openMyDrawer()">Open drawer</button>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Property | Type | Required | Default | Description |
|
|
37
|
+
| --------------------- | ----------------------------------------------------------------- | :------: | ----------- | ------------------------------------------ |
|
|
38
|
+
| component | `Promise<Component>` | ✓ | | `import('path/to/drawer.vue')` |
|
|
39
|
+
| props | `Record<string, MaybeRef<any>>` | | `{}` | Props passed to the drawer component |
|
|
40
|
+
| onConfirm | `(...args: any[]) => TConfirmPayload \| Promise<TConfirmPayload>` | | `undefined` | Called when the drawer is confirmed |
|
|
41
|
+
| onCancel | `(...args: any[]) => TCancelPayload \| Promise<TCancelPayload>` | | `undefined` | Called when the drawer is cancelled |
|
|
42
|
+
| keepOpenOnRouteChange | `boolean` | | `false` | By default the drawer closes on navigation |
|
|
43
|
+
|
|
44
|
+
`useDrawer()` with no arguments returns the raw `openDrawer(id, config)` function for advanced use cases where you manage the drawer ID yourself.
|
|
45
|
+
|
|
46
|
+
### Drawer component
|
|
47
|
+
|
|
48
|
+
A drawer component uses `VtsDrawer` and its sub-components:
|
|
49
|
+
|
|
50
|
+
```vue
|
|
51
|
+
<!-- MyDrawer.vue -->
|
|
52
|
+
<template>
|
|
53
|
+
<VtsDrawer :dismissible>
|
|
54
|
+
<template #title>My Drawer</template>
|
|
55
|
+
<template #content>
|
|
56
|
+
<!-- your content here -->
|
|
57
|
+
</template>
|
|
58
|
+
<template #buttons>
|
|
59
|
+
<VtsDrawerCancelButton />
|
|
60
|
+
<VtsDrawerConfirmButton>Save</VtsDrawerConfirmButton>
|
|
61
|
+
</template>
|
|
62
|
+
</VtsDrawer>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script lang="ts" setup>
|
|
66
|
+
import VtsDrawer from '@core/components/drawer/VtsDrawer.vue'
|
|
67
|
+
import VtsDrawerCancelButton from '@core/components/drawer/VtsDrawerCancelButton.vue'
|
|
68
|
+
import VtsDrawerConfirmButton from '@core/components/drawer/VtsDrawerConfirmButton.vue'
|
|
69
|
+
|
|
70
|
+
defineProps<{
|
|
71
|
+
dismissible?: boolean
|
|
72
|
+
// Injected by VtsDrawerList: true when this drawer is the topmost in the stack.
|
|
73
|
+
// Pass it through to VtsDrawer so only the current drawer reacts to Escape.
|
|
74
|
+
current?: boolean
|
|
75
|
+
}>()
|
|
76
|
+
</script>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`VtsDrawer` props:
|
|
80
|
+
|
|
81
|
+
| Prop | Type | Default | Description |
|
|
82
|
+
| ------------- | --------- | ----------- | --------------------------------------------------------- |
|
|
83
|
+
| `dismissible` | `boolean` | `false` | Allows closing via backdrop click, X button, and Escape |
|
|
84
|
+
| `isOpen` | `boolean` | `undefined` | Controls visibility externally (uncontrolled by default) |
|
|
85
|
+
| `current` | `boolean` | `undefined` | Pass through from parent props — controls Escape handling |
|
|
86
|
+
|
|
87
|
+
When `isOpen` is omitted, `VtsDrawer` manages its own visibility internally (uncontrolled mode). In controlled mode (`:is-open` provided), the `dismiss` event is emitted instead of closing automatically.
|
|
88
|
+
|
|
89
|
+
`VtsDrawerCancelButton` and `VtsDrawerConfirmButton` accept an optional `onClick` prop. When provided, the button emits a `click` event instead of triggering `onCancel`/`onConfirm`:
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<VtsDrawerCancelButton :on-click="handleCustomCancel" />
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Opening with arguments
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const openMyDrawer = useDrawer((name: string) => ({
|
|
99
|
+
component: import('path/to/MyDrawer.vue'),
|
|
100
|
+
props: { name },
|
|
101
|
+
}))
|
|
102
|
+
|
|
103
|
+
openMyDrawer('John')
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Payload and response
|
|
107
|
+
|
|
108
|
+
Payloads are fully typed based on the return type of `onConfirm` and `onCancel`:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const openMyDrawer = useDrawer({
|
|
112
|
+
component: import('path/to/MyDrawer.vue'),
|
|
113
|
+
onConfirm: () => 'saved',
|
|
114
|
+
onCancel: () => 'aborted',
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const result = await openMyDrawer()
|
|
118
|
+
|
|
119
|
+
if (result.confirmed) {
|
|
120
|
+
result.payload // string
|
|
121
|
+
} else {
|
|
122
|
+
result.payload // string
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If `onConfirm` or `onCancel` throws, the promise rejects with the thrown error.
|
|
127
|
+
|
|
128
|
+
### `onConfirm` and `onCancel` event args
|
|
129
|
+
|
|
130
|
+
If your drawer component defines args for `confirm` and `cancel` events, you'll get them as arguments of the `onConfirm` and `onCancel` callbacks:
|
|
131
|
+
|
|
132
|
+
```vue
|
|
133
|
+
<!-- MyDrawer.vue -->
|
|
134
|
+
<script lang="ts" setup>
|
|
135
|
+
defineEmits<{
|
|
136
|
+
confirm: [value: string]
|
|
137
|
+
cancel: []
|
|
138
|
+
}>()
|
|
139
|
+
</script>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const openMyDrawer = useDrawer({
|
|
144
|
+
component: import('path/to/MyDrawer.vue'),
|
|
145
|
+
onConfirm: (value: string) => console.log('Confirmed with', value),
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Busy state
|
|
150
|
+
|
|
151
|
+
If `onConfirm` or `onCancel` returns a `Promise`, the buttons show a busy indicator while the promise is pending.
|
|
152
|
+
|
|
153
|
+
**Important asymmetry:**
|
|
154
|
+
|
|
155
|
+
- `onConfirm` closes the drawer **optimistically** before the async work runs. The drawer is gone before `await` settles.
|
|
156
|
+
- `onCancel` waits for the async work to complete **before** closing the drawer.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const openMyDrawer = useDrawer({
|
|
160
|
+
component: import('path/to/MyDrawer.vue'),
|
|
161
|
+
onConfirm: async () => {
|
|
162
|
+
await saveToServer() // drawer is already closed while this runs
|
|
163
|
+
},
|
|
164
|
+
onCancel: async () => {
|
|
165
|
+
await cleanup() // drawer stays open while this runs, then closes
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Aborting close
|
|
171
|
+
|
|
172
|
+
Return `ABORT_DRAWER` from `onCancel` to keep the drawer open (e.g. to show a confirmation prompt):
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const openMyDrawer = useDrawer({
|
|
176
|
+
component: import('path/to/MyDrawer.vue'),
|
|
177
|
+
onCancel: async () => {
|
|
178
|
+
if (!hasUnsavedChanges()) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
const confirmed = await confirmDiscard()
|
|
182
|
+
if (!confirmed) {
|
|
183
|
+
return ABORT_DRAWER // drawer stays open
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> **Note:** Returning `ABORT_DRAWER` from `onConfirm` does **not** keep the drawer open. The drawer is closed optimistically before `onConfirm` runs. Returning `ABORT_DRAWER` permanently prevents the promise from resolving — avoid it in `onConfirm`.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useDrawerStore } from '@core/packages/drawer/drawer.store.ts'
|
|
2
|
+
import {
|
|
3
|
+
ABORT_DRAWER,
|
|
4
|
+
DrawerCancelResponse,
|
|
5
|
+
type DrawerConfig,
|
|
6
|
+
DrawerConfirmResponse,
|
|
7
|
+
type DrawerHandlerArgs,
|
|
8
|
+
type DrawerResponse,
|
|
9
|
+
} from '@core/packages/drawer/types.ts'
|
|
10
|
+
import { computed, defineAsyncComponent, reactive, ref, watch } from 'vue'
|
|
11
|
+
import { useRoute } from 'vue-router'
|
|
12
|
+
|
|
13
|
+
export function createDrawerOpener() {
|
|
14
|
+
const drawerStore = useDrawerStore()
|
|
15
|
+
|
|
16
|
+
const route = useRoute()
|
|
17
|
+
|
|
18
|
+
const ids = new Set<symbol | string>()
|
|
19
|
+
const cancelers = new Map<string | symbol, () => void>()
|
|
20
|
+
|
|
21
|
+
function closeById(id: string | symbol) {
|
|
22
|
+
drawerStore.removeDrawer(id)
|
|
23
|
+
ids.delete(id)
|
|
24
|
+
cancelers.delete(id)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (route !== undefined) {
|
|
28
|
+
watch(
|
|
29
|
+
() => route.path,
|
|
30
|
+
() => {
|
|
31
|
+
ids.forEach(id => {
|
|
32
|
+
if (!drawerStore.getDrawer(id)?.keepOpenOnRouteChange) {
|
|
33
|
+
cancelers.get(id)?.()
|
|
34
|
+
closeById(id)
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return function openDrawer<
|
|
42
|
+
TProps,
|
|
43
|
+
TConfirmArgs extends DrawerHandlerArgs<TProps, 'onConfirm'>,
|
|
44
|
+
TCancelArgs extends DrawerHandlerArgs<TProps, 'onCancel'>,
|
|
45
|
+
TConfirmPayload = undefined,
|
|
46
|
+
TCancelPayload = undefined,
|
|
47
|
+
>(id: string | symbol, config: DrawerConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>) {
|
|
48
|
+
const close = () => closeById(id)
|
|
49
|
+
|
|
50
|
+
const promise = new Promise<DrawerResponse<TConfirmPayload, TCancelPayload>>((resolve, reject) => {
|
|
51
|
+
const isBusy = ref(false)
|
|
52
|
+
|
|
53
|
+
cancelers.set(id, () => resolve(new DrawerCancelResponse(undefined as TCancelPayload)))
|
|
54
|
+
|
|
55
|
+
drawerStore.addDrawer({
|
|
56
|
+
id,
|
|
57
|
+
component: defineAsyncComponent({
|
|
58
|
+
loader: () => config.component,
|
|
59
|
+
onError(_error, _retry, fail) {
|
|
60
|
+
fail()
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
isBusy: computed(() => isBusy.value),
|
|
64
|
+
props: reactive(config.props ?? {}),
|
|
65
|
+
keepOpenOnRouteChange: config.keepOpenOnRouteChange ?? false,
|
|
66
|
+
onConfirm: async (...args: TConfirmArgs) => {
|
|
67
|
+
try {
|
|
68
|
+
isBusy.value = true
|
|
69
|
+
|
|
70
|
+
close()
|
|
71
|
+
|
|
72
|
+
const result = config.onConfirm ? await config.onConfirm(...args) : (undefined as TConfirmPayload)
|
|
73
|
+
|
|
74
|
+
if (result === ABORT_DRAWER || result instanceof DrawerCancelResponse) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (result instanceof DrawerConfirmResponse) {
|
|
79
|
+
resolve(result)
|
|
80
|
+
} else {
|
|
81
|
+
resolve(new DrawerConfirmResponse(result))
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
reject(error)
|
|
85
|
+
} finally {
|
|
86
|
+
isBusy.value = false
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
onCancel: async (...args: TCancelArgs) => {
|
|
90
|
+
try {
|
|
91
|
+
isBusy.value = true
|
|
92
|
+
|
|
93
|
+
const result = config.onCancel ? await config.onCancel(...args) : (undefined as TCancelPayload)
|
|
94
|
+
|
|
95
|
+
if (result === ABORT_DRAWER || result instanceof DrawerCancelResponse) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (result instanceof DrawerConfirmResponse) {
|
|
100
|
+
resolve(new DrawerCancelResponse(result.payload))
|
|
101
|
+
} else {
|
|
102
|
+
resolve(new DrawerCancelResponse(result))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
close()
|
|
106
|
+
} catch (error) {
|
|
107
|
+
reject(error)
|
|
108
|
+
} finally {
|
|
109
|
+
isBusy.value = false
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
ids.add(id)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return Object.assign(promise, { close })
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RegisteredDrawer } from '@core/packages/drawer/types.ts'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import { computed, shallowReactive } from 'vue'
|
|
4
|
+
|
|
5
|
+
export const useDrawerStore = defineStore('drawer', () => {
|
|
6
|
+
const drawers = shallowReactive(new Map<symbol | string, RegisteredDrawer>())
|
|
7
|
+
|
|
8
|
+
const addDrawer = (drawer: RegisteredDrawer) => {
|
|
9
|
+
drawers.set(drawer.id, drawer)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const removeDrawer = (id: symbol | string) => {
|
|
13
|
+
drawers.delete(id)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
drawers: computed(() => Array.from(drawers.values())),
|
|
18
|
+
getDrawer: (id: symbol | string) => drawers.get(id),
|
|
19
|
+
addDrawer,
|
|
20
|
+
removeDrawer,
|
|
21
|
+
}
|
|
22
|
+
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Component, ComputedRef, InjectionKey, MaybeRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type DrawerPropsOption<TProps> = {
|
|
4
|
+
[K in keyof TProps]: MaybeRef<TProps[K]>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type MaybePromise<T> = T | Promise<T>
|
|
8
|
+
|
|
9
|
+
export type AreAllPropsOptional<TProps> = Record<string, never> extends TProps ? true : false
|
|
10
|
+
|
|
11
|
+
export type RegisteredDrawer = {
|
|
12
|
+
id: symbol | string
|
|
13
|
+
component: Component
|
|
14
|
+
keepOpenOnRouteChange: boolean
|
|
15
|
+
props: object
|
|
16
|
+
isBusy: ComputedRef<boolean>
|
|
17
|
+
onConfirm: (...args: any[]) => void | Promise<void>
|
|
18
|
+
onCancel: (...args: any[]) => void | Promise<void>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type DrawerHandlerArgs<TProps, THandler extends 'onConfirm' | 'onCancel'> = TProps extends {
|
|
22
|
+
[K in THandler]?: (...args: infer TArgs) => any
|
|
23
|
+
}
|
|
24
|
+
? TArgs
|
|
25
|
+
: never
|
|
26
|
+
|
|
27
|
+
export abstract class AbstractDrawerResponse<TPayload> {
|
|
28
|
+
constructor(public readonly payload: TPayload) {}
|
|
29
|
+
|
|
30
|
+
abstract get confirmed(): boolean
|
|
31
|
+
abstract get canceled(): boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class DrawerConfirmResponse<TPayload> extends AbstractDrawerResponse<TPayload> {
|
|
35
|
+
public readonly confirmed = true
|
|
36
|
+
public readonly canceled = false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class DrawerCancelResponse<TPayload> extends AbstractDrawerResponse<TPayload> {
|
|
40
|
+
public readonly confirmed = false
|
|
41
|
+
public readonly canceled = true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type DrawerResponse<TConfirmPayload, TCancelPayload> =
|
|
45
|
+
| DrawerConfirmResponse<TConfirmPayload>
|
|
46
|
+
| DrawerCancelResponse<TCancelPayload>
|
|
47
|
+
|
|
48
|
+
export const ABORT_DRAWER = Symbol('abort drawer')
|
|
49
|
+
|
|
50
|
+
export type OpenDrawerReturn<TConfirmPayload, TCancelPayload> = Promise<
|
|
51
|
+
DrawerResponse<TConfirmPayload, TCancelPayload>
|
|
52
|
+
> & {
|
|
53
|
+
close: () => void
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const IK_DRAWER = Symbol('drawer') as InjectionKey<ComputedRef<RegisteredDrawer>>
|
|
57
|
+
|
|
58
|
+
export type DrawerPropsConfigEntry<TProps> =
|
|
59
|
+
AreAllPropsOptional<TProps> extends true
|
|
60
|
+
? { props?: DrawerPropsOption<TProps> }
|
|
61
|
+
: { props: DrawerPropsOption<TProps> }
|
|
62
|
+
|
|
63
|
+
export type DrawerConfig<
|
|
64
|
+
TProps,
|
|
65
|
+
TConfirmArgs extends any[],
|
|
66
|
+
TCancelArgs extends any[],
|
|
67
|
+
TConfirmPayload,
|
|
68
|
+
TCancelPayload,
|
|
69
|
+
> = {
|
|
70
|
+
component: Promise<{
|
|
71
|
+
default: abstract new () => {
|
|
72
|
+
$props: TProps
|
|
73
|
+
}
|
|
74
|
+
}>
|
|
75
|
+
onConfirm?: (...args: TConfirmArgs) => MaybePromise<DrawerResponse<TConfirmPayload, any> | TConfirmPayload>
|
|
76
|
+
onCancel?: (...args: TCancelArgs) => MaybePromise<DrawerResponse<TCancelPayload, any> | TCancelPayload>
|
|
77
|
+
keepOpenOnRouteChange?: boolean
|
|
78
|
+
} & DrawerPropsConfigEntry<TProps>
|
|
79
|
+
|
|
80
|
+
export type UseDrawerReturn<TArgs extends any[] = any[], TConfirmPayload = any, TCancelPayload = any> = (
|
|
81
|
+
...args: TArgs
|
|
82
|
+
) => OpenDrawerReturn<TConfirmPayload, TCancelPayload>
|
|
83
|
+
|
|
84
|
+
export type OpenDrawer = <
|
|
85
|
+
TProps,
|
|
86
|
+
TConfirmArgs extends DrawerHandlerArgs<TProps, 'onConfirm'>,
|
|
87
|
+
TCancelArgs extends DrawerHandlerArgs<TProps, 'onCancel'>,
|
|
88
|
+
TConfirmPayload = undefined,
|
|
89
|
+
TCancelPayload = undefined,
|
|
90
|
+
>(
|
|
91
|
+
id: string,
|
|
92
|
+
config: DrawerConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
|
|
93
|
+
) => OpenDrawerReturn<TConfirmPayload, TCancelPayload>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createDrawerOpener } from '@core/packages/drawer/create-drawer-opener.ts'
|
|
2
|
+
import { type DrawerConfig, type DrawerHandlerArgs, type UseDrawerReturn } from '@core/packages/drawer/types.ts'
|
|
3
|
+
|
|
4
|
+
export function useDrawer(): ReturnType<typeof createDrawerOpener>
|
|
5
|
+
|
|
6
|
+
export function useDrawer<
|
|
7
|
+
TProps,
|
|
8
|
+
TConfirmArgs extends DrawerHandlerArgs<TProps, 'onConfirm'>,
|
|
9
|
+
TCancelArgs extends DrawerHandlerArgs<TProps, 'onCancel'>,
|
|
10
|
+
TConfirmPayload = undefined,
|
|
11
|
+
TCancelPayload = undefined,
|
|
12
|
+
>(
|
|
13
|
+
config: DrawerConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
|
|
14
|
+
): UseDrawerReturn<[], TConfirmPayload, TCancelPayload>
|
|
15
|
+
|
|
16
|
+
export function useDrawer<
|
|
17
|
+
TConfigBuilderArgs extends any[],
|
|
18
|
+
TProps,
|
|
19
|
+
TConfirmArgs extends DrawerHandlerArgs<TProps, 'onConfirm'>,
|
|
20
|
+
TCancelArgs extends DrawerHandlerArgs<TProps, 'onCancel'>,
|
|
21
|
+
TConfirmPayload = undefined,
|
|
22
|
+
TCancelPayload = undefined,
|
|
23
|
+
>(
|
|
24
|
+
configBuilder: (
|
|
25
|
+
...args: TConfigBuilderArgs
|
|
26
|
+
) => DrawerConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
|
|
27
|
+
): UseDrawerReturn<TConfigBuilderArgs, TConfirmPayload, TCancelPayload>
|
|
28
|
+
|
|
29
|
+
export function useDrawer<
|
|
30
|
+
TConfigBuilderArgs extends any[],
|
|
31
|
+
TProps,
|
|
32
|
+
TConfirmArgs extends DrawerHandlerArgs<TProps, 'onConfirm'>,
|
|
33
|
+
TCancelArgs extends DrawerHandlerArgs<TProps, 'onCancel'>,
|
|
34
|
+
TConfig extends DrawerConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>,
|
|
35
|
+
TConfirmPayload = undefined,
|
|
36
|
+
TCancelPayload = undefined,
|
|
37
|
+
>(
|
|
38
|
+
configOrBuilder?: TConfig | ((...args: TConfigBuilderArgs) => TConfig)
|
|
39
|
+
): UseDrawerReturn<TConfigBuilderArgs, TConfirmPayload, TCancelPayload> | ReturnType<typeof createDrawerOpener> {
|
|
40
|
+
const openDrawer = createDrawerOpener()
|
|
41
|
+
|
|
42
|
+
if (configOrBuilder === undefined) {
|
|
43
|
+
return openDrawer
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const id = Symbol('drawer')
|
|
47
|
+
|
|
48
|
+
return (...args: TConfigBuilderArgs) => {
|
|
49
|
+
const config = typeof configOrBuilder === 'function' ? configOrBuilder(...args) : configOrBuilder
|
|
50
|
+
|
|
51
|
+
return openDrawer(id, config)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRule, type Maybe } from '@regle/core'
|
|
2
|
+
import { isFilled } from '@regle/rules'
|
|
3
|
+
|
|
4
|
+
const IPV4_OCTET = '(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)'
|
|
5
|
+
const IPV4_CIDR_PREFIX = '(3[0-2]|[12]?\\d)'
|
|
6
|
+
const IP_OR_CIDR_REGEX = new RegExp(`^${IPV4_OCTET}(\\.${IPV4_OCTET}){3}(/${IPV4_CIDR_PREFIX})?$`)
|
|
7
|
+
|
|
8
|
+
export const ipv4OrCidr = createRule({
|
|
9
|
+
validator(value: Maybe<string>) {
|
|
10
|
+
if (!isFilled(value)) {
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return IP_OR_CIDR_REGEX.test(value.trim())
|
|
15
|
+
},
|
|
16
|
+
message: 'Invalid IP address',
|
|
17
|
+
})
|
|
@@ -22,16 +22,18 @@ export function createModalOpener() {
|
|
|
22
22
|
ids.delete(id)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
if (route !== undefined) {
|
|
26
|
+
watch(
|
|
27
|
+
() => route.path,
|
|
28
|
+
() => {
|
|
29
|
+
ids.forEach(id => {
|
|
30
|
+
if (!modalStore.getModal(id)?.keepOpenOnRouteChange) {
|
|
31
|
+
closeById(id)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
}
|
|
35
37
|
|
|
36
38
|
return function openModal<
|
|
37
39
|
TProps,
|
|
@@ -9,7 +9,7 @@ import { h, toValue } from 'vue'
|
|
|
9
9
|
export type { ActionItem }
|
|
10
10
|
|
|
11
11
|
export const useActionColumn = defineColumn((config: HeaderConfig & Partial<ButtonIconConfig>) => ({
|
|
12
|
-
renderHead: () => renderHeadCell(config.
|
|
12
|
+
renderHead: () => renderHeadCell(config.headerLabel),
|
|
13
13
|
renderBody: (params: { onClick: () => void; actions?: ActionItem[] }) =>
|
|
14
14
|
h(VtsActionCell, {
|
|
15
15
|
buttonIcon: toValue(config.buttonIcon ?? 'fa:eye'),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { useCollapsedListColumn } from '@core/tables/column-definitions/collapsed-list-column.ts'
|
|
2
2
|
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
3
3
|
|
|
4
|
-
export const useAddressColumn = (config?: HeaderConfig) => useCollapsedListColumn(
|
|
4
|
+
export const useAddressColumn = (config?: HeaderConfig) => useCollapsedListColumn(config)
|
|
@@ -17,7 +17,7 @@ type ButtonConfig = {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const useButtonColumn = defineColumn((config?: HeaderConfig & ButtonConfig) => ({
|
|
20
|
-
renderHead: () => renderHeadCell(config?.
|
|
20
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
21
21
|
renderBody: (label: string, onClick: () => void, props?: Partial<ButtonProps>) =>
|
|
22
22
|
renderBodyCell(() =>
|
|
23
23
|
h(
|
|
@@ -16,7 +16,7 @@ export type ButtonIconConfig = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export const useButtonIconColumn = defineColumn((config: HeaderConfig & ButtonIconConfig) => ({
|
|
19
|
-
renderHead: () => renderHeadCell(config.
|
|
19
|
+
renderHead: () => renderHeadCell(config.headerLabel),
|
|
20
20
|
renderBody: (onClick: () => void) =>
|
|
21
21
|
h(UiTableCell, { align: 'center', style: 'width: 6rem' }, () =>
|
|
22
22
|
h(UiButtonIcon, {
|
|
@@ -7,6 +7,6 @@ import { toArray } from '@core/utils/to-array.utils.ts'
|
|
|
7
7
|
import { h } from 'vue'
|
|
8
8
|
|
|
9
9
|
export const useCollapsedListColumn = defineColumn((config?: HeaderConfig) => ({
|
|
10
|
-
renderHead: () => renderHeadCell(config?.
|
|
10
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
11
11
|
renderBody: (items: MaybeArray<string>) => h(VtsCollapsedListCell, { items: toArray(items) }),
|
|
12
12
|
}))
|
|
@@ -13,7 +13,7 @@ export const useDateColumn = defineColumn((config?: HeaderConfig & DateConfig) =
|
|
|
13
13
|
const { d } = useI18n()
|
|
14
14
|
|
|
15
15
|
return {
|
|
16
|
-
renderHead: () => renderHeadCell(config?.
|
|
16
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
17
17
|
renderBody: (date?: DateLike, options?: { relative?: boolean }) => {
|
|
18
18
|
return renderBodyCell(() => {
|
|
19
19
|
if (date === undefined) {
|
|
@@ -5,7 +5,7 @@ import type { HeaderConfig } from '@core/tables/types.ts'
|
|
|
5
5
|
import { h } from 'vue'
|
|
6
6
|
|
|
7
7
|
export const useDoubleLinkColumn = defineColumn((config?: HeaderConfig) => ({
|
|
8
|
-
renderHead: () => renderHeadCell(config?.
|
|
8
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
9
9
|
renderBody: (link: { label: string } & VtsDoubleLinkCellProps) => {
|
|
10
10
|
const { label, ...doubleLinkCellProps } = link
|
|
11
11
|
|
|
@@ -7,6 +7,6 @@ import type { HeaderConfig } from '@core/tables/types.ts'
|
|
|
7
7
|
import { h } from 'vue'
|
|
8
8
|
|
|
9
9
|
export const useInfoColumn = defineColumn((config?: HeaderConfig) => ({
|
|
10
|
-
renderHead: () => renderHeadCell(config?.
|
|
10
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
11
11
|
renderBody: (label: string, accent: InfoAccent) => renderBodyCell(() => h(UiInfo, { accent }, () => label)),
|
|
12
12
|
}))
|
|
@@ -11,11 +11,7 @@ type InputConfig = {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export const useInputColumn = defineColumn((config?: HeaderConfig & InputConfig) => ({
|
|
14
|
-
renderHead: () =>
|
|
15
|
-
renderHeadCell(
|
|
16
|
-
config?.headerIcon ?? (config?.type === 'number' ? 'fa:hashtag' : 'fa:align-left'),
|
|
17
|
-
config?.headerLabel
|
|
18
|
-
),
|
|
14
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
19
15
|
renderBody: (model: Ref<string | number | undefined>, inputProps?: { disabled?: boolean }) =>
|
|
20
16
|
renderBodyCell(() =>
|
|
21
17
|
h(UiInput, {
|
|
@@ -5,7 +5,7 @@ import type { HeaderConfig } from '@core/tables/types.ts'
|
|
|
5
5
|
import { h } from 'vue'
|
|
6
6
|
|
|
7
7
|
export const useLinkColumn = defineColumn((config?: HeaderConfig) => ({
|
|
8
|
-
renderHead: () => renderHeadCell(config?.
|
|
8
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
9
9
|
renderBody: (link: { label: string } & VtsLinkCellProps) => {
|
|
10
10
|
const { label, ...linkCellProps } = link
|
|
11
11
|
|
|
@@ -6,7 +6,7 @@ import type { HeaderConfig } from '@core/tables/types.ts'
|
|
|
6
6
|
import { h } from 'vue'
|
|
7
7
|
|
|
8
8
|
export const useLinkOrTextColumn = defineColumn((config?: HeaderConfig) => ({
|
|
9
|
-
renderHead: () => renderHeadCell(config?.
|
|
9
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
10
10
|
renderBody: (link: { label: string; suffix?: string } & VtsLinkCellProps) => {
|
|
11
11
|
const { label, suffix, ...linkCellProps } = link
|
|
12
12
|
|
|
@@ -4,6 +4,6 @@ import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
|
4
4
|
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
5
|
|
|
6
6
|
export const useLiteralColumn = defineColumn((config?: HeaderConfig) => ({
|
|
7
|
-
renderHead: () => renderHeadCell(config?.
|
|
7
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
8
8
|
renderBody: (value: any) => renderBodyCell(() => value),
|
|
9
9
|
}))
|