@xen-orchestra/web-core 0.54.0 → 0.56.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/card-object-title/VtsCardObjectTitle.vue +36 -0
- package/lib/components/code-snippet/VtsCodeSnippet.vue +11 -5
- package/lib/components/drawer/VtsDrawer.vue +89 -0
- package/lib/components/drawer/VtsDrawerButton.vue +22 -0
- package/lib/components/drawer/VtsDrawerCancelButton.vue +33 -0
- package/lib/components/drawer/VtsDrawerConfirmButton.vue +32 -0
- package/lib/components/drawer/VtsDrawerList.vue +34 -0
- package/lib/components/icon/VtsIcon.vue +3 -1
- package/lib/components/layout/VtsContentSidePanel.vue +41 -0
- package/lib/components/layout/VtsLayoutSidebar.vue +5 -2
- package/lib/components/menu/VtsActionsMenu.vue +37 -3
- package/lib/components/modal/VtsModalButton.vue +0 -1
- package/lib/components/operation-card/VtsOperationCard.vue +2 -1
- package/lib/components/panel/VtsPanel.vue +80 -0
- package/lib/components/panel/VtsSidePanel.vue +140 -0
- package/lib/components/query-builder/VtsQueryBuilderModal.vue +0 -1
- package/lib/components/select/VtsSelect.vue +1 -0
- package/lib/components/state-hero/VtsStateHero.vue +26 -24
- package/lib/components/table/VtsTable.vue +2 -1
- package/lib/components/table/cells/VtsActionCell.vue +7 -0
- package/lib/components/table/cells/VtsHeaderCell.vue +1 -17
- package/lib/components/table/cells/VtsLinkCell.vue +3 -1
- package/lib/components/table/cells/VtsTagCell.vue +4 -2
- package/lib/components/tree/VtsTreeItem.vue +9 -3
- package/lib/components/tree/VtsTreeList.vue +6 -3
- package/lib/components/tree/VtsTreeLoadingItem.vue +3 -3
- package/lib/components/ui/drawer/UiDrawer.vue +123 -0
- package/lib/components/ui/head-bar/UiHeadBar.vue +1 -1
- package/lib/components/ui/input/UiInput.vue +3 -1
- package/lib/components/ui/link/UiLink.vue +3 -1
- package/lib/components/ui/modal/UiModal.vue +10 -18
- package/lib/components/ui/panel/UiPanel.vue +7 -24
- package/lib/components/ui/tree-item-label/UiTreeItemLabel.vue +1 -1
- package/lib/composables/default-tab.composable.ts +4 -0
- package/lib/composables/table-state.composable.ts +1 -1
- package/lib/icons/action-icons.ts +20 -2
- package/lib/icons/custom-icons.ts +16 -0
- package/lib/icons/object-icons.ts +33 -18
- package/lib/locales/cs.json +110 -11
- package/lib/locales/da.json +1 -3
- package/lib/locales/de.json +1 -12
- package/lib/locales/en.json +76 -12
- package/lib/locales/es.json +15 -6
- package/lib/locales/fa.json +0 -1
- package/lib/locales/fr.json +76 -12
- package/lib/locales/it.json +0 -1
- package/lib/locales/ko.json +0 -2
- package/lib/locales/nb-NO.json +18 -20
- package/lib/locales/nl.json +0 -11
- package/lib/locales/pt-BR.json +0 -4
- package/lib/locales/pt.json +0 -4
- package/lib/locales/ru.json +0 -1
- package/lib/locales/sk.json +110 -11
- package/lib/locales/sv.json +2 -13
- package/lib/locales/uk.json +0 -1
- package/lib/locales/zh-Hans.json +79 -16
- 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/modal/create-modal-opener.ts +12 -10
- package/lib/packages/remote-resource/define-remote-resource.ts +64 -29
- package/lib/packages/tree/types.ts +6 -0
- package/lib/packages/tree/use-tree.ts +38 -37
- package/lib/stores/panel.store.ts +56 -5
- package/lib/tables/column-definitions/action-column.ts +22 -10
- 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/column-sets/vm-columns.ts +2 -3
- 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/state-hero.type.ts +19 -0
- package/lib/types/storage-repository.type.ts +14 -0
- package/lib/types/vue-virtual-scroller.d.ts +8 -2
- package/lib/utils/injection-keys.util.ts +1 -1
- package/lib/utils/sr.utils.ts +34 -0
- package/package.json +4 -4
|
@@ -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
|
+
}
|
|
@@ -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,
|
|
@@ -14,19 +14,19 @@ import {
|
|
|
14
14
|
computed,
|
|
15
15
|
type ComputedRef,
|
|
16
16
|
type EffectScope,
|
|
17
|
+
effectScope,
|
|
17
18
|
getCurrentScope,
|
|
19
|
+
type MaybeRef,
|
|
18
20
|
type MaybeRefOrGetter,
|
|
19
21
|
onScopeDispose,
|
|
20
22
|
reactive,
|
|
21
23
|
type Ref,
|
|
22
24
|
ref,
|
|
23
25
|
shallowRef,
|
|
24
|
-
triggerRef,
|
|
25
26
|
toRef,
|
|
26
27
|
toValue,
|
|
28
|
+
triggerRef,
|
|
27
29
|
watch,
|
|
28
|
-
effectScope,
|
|
29
|
-
type MaybeRef,
|
|
30
30
|
} from 'vue'
|
|
31
31
|
|
|
32
32
|
const DEFAULT_CACHE_EXPIRATION_MS = 10_000
|
|
@@ -41,7 +41,12 @@ export function defineRemoteResource<
|
|
|
41
41
|
url: string | ((...args: TArgs) => string)
|
|
42
42
|
initialData: () => TData
|
|
43
43
|
state?: (data: Ref<NoInfer<TData>>, context: ResourceContext<TArgs>) => TState
|
|
44
|
-
onDataReceived?: (
|
|
44
|
+
onDataReceived?: (
|
|
45
|
+
data: Ref<NoInfer<TData>>,
|
|
46
|
+
receivedData: any,
|
|
47
|
+
calledFrom: 'execute' | 'update',
|
|
48
|
+
context?: ResourceContext<TArgs>
|
|
49
|
+
) => void
|
|
45
50
|
cacheExpirationMs?: number | false
|
|
46
51
|
pollingIntervalMs?: number | false
|
|
47
52
|
stream?: boolean
|
|
@@ -50,7 +55,12 @@ export function defineRemoteResource<
|
|
|
50
55
|
export function defineRemoteResource<TData, TState extends object, TArgs extends any[] = []>(config: {
|
|
51
56
|
url: string | ((...args: TArgs) => string)
|
|
52
57
|
state?: (data: Ref<TData | undefined>, context: ResourceContext<TArgs>) => TState
|
|
53
|
-
onDataReceived?: (
|
|
58
|
+
onDataReceived?: (
|
|
59
|
+
data: Ref<TData | undefined>,
|
|
60
|
+
receivedData: any,
|
|
61
|
+
calledFrom: 'execute' | 'update',
|
|
62
|
+
context?: ResourceContext<TArgs>
|
|
63
|
+
) => void
|
|
54
64
|
cacheExpirationMs?: number | false
|
|
55
65
|
pollingIntervalMs?: number | false
|
|
56
66
|
stream?: boolean
|
|
@@ -64,7 +74,12 @@ export function defineRemoteResource<
|
|
|
64
74
|
url: string | ((...args: TArgs) => string)
|
|
65
75
|
initialData: () => TData
|
|
66
76
|
state?: (data: Ref<NoInfer<TData>>, context: ResourceContext<TArgs>) => TState
|
|
67
|
-
onDataReceived?: (
|
|
77
|
+
onDataReceived?: (
|
|
78
|
+
data: Ref<NoInfer<TData>>,
|
|
79
|
+
receivedData: any,
|
|
80
|
+
calledFrom: 'execute' | 'update',
|
|
81
|
+
context?: ResourceContext<TArgs>
|
|
82
|
+
) => void
|
|
68
83
|
onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
|
69
84
|
stream?: boolean
|
|
70
85
|
initWatchCollection: () => {
|
|
@@ -86,7 +101,12 @@ export function defineRemoteResource<
|
|
|
86
101
|
url: string | ((...args: TArgs) => string)
|
|
87
102
|
initialData?: () => TData
|
|
88
103
|
state?: (data: Ref<TData>, context: ResourceContext<TArgs>) => TState
|
|
89
|
-
onDataReceived?: (
|
|
104
|
+
onDataReceived?: (
|
|
105
|
+
data: Ref<NoInfer<TData>>,
|
|
106
|
+
receivedData: any,
|
|
107
|
+
calledFrom: 'execute' | 'update',
|
|
108
|
+
context?: ResourceContext<TArgs>
|
|
109
|
+
) => void
|
|
90
110
|
onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
|
91
111
|
cacheExpirationMs?: number | false
|
|
92
112
|
pollingIntervalMs?: number | false
|
|
@@ -161,33 +181,38 @@ export function defineRemoteResource<
|
|
|
161
181
|
|
|
162
182
|
const onDataReceived =
|
|
163
183
|
config.onDataReceived ??
|
|
164
|
-
((data: Ref<TData>, receivedData: any, context?: ResourceContext<TArgs>) => {
|
|
184
|
+
((data: Ref<TData>, receivedData: any, calledFrom: 'execute' | 'update', context?: ResourceContext<TArgs>) => {
|
|
165
185
|
// allow to ignore some update (like for sub collection. E.g. vms/:id/vdis)
|
|
166
186
|
if (watchCollection?.predicate?.(receivedData, context) === false) {
|
|
167
187
|
return
|
|
168
188
|
}
|
|
169
189
|
|
|
170
|
-
if (
|
|
171
|
-
data.value
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
isBufferEventsProcessed = true
|
|
190
|
+
if (!Array.isArray(data.value)) {
|
|
191
|
+
if (data.value === undefined) {
|
|
192
|
+
data.value = receivedData
|
|
193
|
+
} else {
|
|
194
|
+
merge(data.value, receivedData)
|
|
176
195
|
}
|
|
196
|
+
|
|
177
197
|
return
|
|
178
198
|
}
|
|
179
199
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
data.value.push(receivedData)
|
|
186
|
-
}
|
|
200
|
+
const store = data.value as TData[]
|
|
201
|
+
|
|
202
|
+
if (Array.isArray(receivedData)) {
|
|
203
|
+
data.value = receivedData as TData
|
|
204
|
+
|
|
187
205
|
return
|
|
188
206
|
}
|
|
189
207
|
|
|
190
|
-
|
|
208
|
+
if (calledFrom === 'execute') {
|
|
209
|
+
store.push(receivedData)
|
|
210
|
+
} else if (!isBufferEventsProcessed) {
|
|
211
|
+
bufferedEvents.push(['update', receivedData])
|
|
212
|
+
} else {
|
|
213
|
+
removeData(store, receivedData)
|
|
214
|
+
store.push(receivedData)
|
|
215
|
+
}
|
|
191
216
|
})
|
|
192
217
|
|
|
193
218
|
const onDataRemoved =
|
|
@@ -292,13 +317,23 @@ export function defineRemoteResource<
|
|
|
292
317
|
}
|
|
293
318
|
|
|
294
319
|
if (config.stream) {
|
|
320
|
+
if (Array.isArray(data.value)) {
|
|
321
|
+
data.value = [] as TData
|
|
322
|
+
}
|
|
323
|
+
|
|
295
324
|
for await (const event of readNDJSONStream(response.body)) {
|
|
296
|
-
onDataReceived(data, event)
|
|
297
|
-
flushData()
|
|
325
|
+
onDataReceived(data, event, 'execute', context)
|
|
326
|
+
void flushData()
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (watchCollection !== undefined && Array.isArray(data.value)) {
|
|
330
|
+
handleBuffer(data as Ref<TData[]>)
|
|
331
|
+
isBufferEventsProcessed = true
|
|
332
|
+
void flushData()
|
|
298
333
|
}
|
|
299
334
|
} else {
|
|
300
|
-
onDataReceived(data, await response.json())
|
|
301
|
-
flushData()
|
|
335
|
+
onDataReceived(data, await response.json(), 'execute', context)
|
|
336
|
+
void flushData()
|
|
302
337
|
}
|
|
303
338
|
|
|
304
339
|
isReady.value = true
|
|
@@ -324,12 +359,12 @@ export function defineRemoteResource<
|
|
|
324
359
|
handlePost,
|
|
325
360
|
resource,
|
|
326
361
|
onDataReceived: receivedData => {
|
|
327
|
-
onDataReceived(data, receivedData, context)
|
|
328
|
-
flushData()
|
|
362
|
+
onDataReceived(data, receivedData, 'update', context)
|
|
363
|
+
void flushData()
|
|
329
364
|
},
|
|
330
365
|
onDataRemoved: receivedData => {
|
|
331
366
|
onDataRemoved(data, receivedData, context)
|
|
332
|
-
flushData()
|
|
367
|
+
void flushData()
|
|
333
368
|
},
|
|
334
369
|
})
|
|
335
370
|
await execute()
|
|
@@ -24,6 +24,12 @@ export type TreeNode<
|
|
|
24
24
|
TDiscriminator = any,
|
|
25
25
|
> = Leaf<TData, TDiscriminator> | Branch<TData, TChildNode, TDiscriminator>
|
|
26
26
|
|
|
27
|
+
export type FlatTreeNode<TNode extends TreeNode = TreeNode> = {
|
|
28
|
+
id: TreeNodeId
|
|
29
|
+
node: TNode
|
|
30
|
+
depth: number
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
export type BaseTreeNodeOptions<TData extends object, TDiscriminator> = {
|
|
28
34
|
discriminator?: TDiscriminator
|
|
29
35
|
predicate?: (node: TreeNodeBase<TData, TDiscriminator>) => boolean | undefined
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { buildNodes } from '@core/packages/tree/build-nodes'
|
|
2
2
|
import type {
|
|
3
3
|
DefinitionToTreeNode,
|
|
4
|
+
FlatTreeNode,
|
|
4
5
|
TreeContext,
|
|
5
6
|
TreeNode,
|
|
6
7
|
TreeNodeDefinition,
|
|
7
8
|
TreeNodeId,
|
|
8
9
|
UseTreeOptions,
|
|
9
10
|
} from '@core/packages/tree/types'
|
|
10
|
-
import {
|
|
11
|
-
import { computed, type MaybeRefOrGetter, nextTick, reactive, ref, toValue } from 'vue'
|
|
11
|
+
import { computed, type MaybeRefOrGetter, reactive, ref, toValue } from 'vue'
|
|
12
12
|
|
|
13
13
|
export function useTree<
|
|
14
14
|
TDefinition extends TreeNodeDefinition,
|
|
@@ -53,6 +53,32 @@ export function useTree<
|
|
|
53
53
|
|
|
54
54
|
const visibleNodes = computed(() => nodes.value.filter(node => !node.isExcluded))
|
|
55
55
|
|
|
56
|
+
const flatNodes = computed<FlatTreeNode[]>(() => {
|
|
57
|
+
const result: FlatTreeNode[] = []
|
|
58
|
+
|
|
59
|
+
const flatten = (list: TreeNode[]) => {
|
|
60
|
+
for (const node of list) {
|
|
61
|
+
result.push({ id: node.id, node, depth: node.depth })
|
|
62
|
+
if (node.isBranch && !node.isCollapsed) {
|
|
63
|
+
flatten(node.children)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
flatten(visibleNodes.value)
|
|
69
|
+
|
|
70
|
+
return result
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const flatNodeIndexById = computed(() => {
|
|
74
|
+
const index = new Map<TreeNodeId, number>()
|
|
75
|
+
const list = flatNodes.value
|
|
76
|
+
for (let i = 0; i < list.length; i++) {
|
|
77
|
+
index.set(list[i].id, i)
|
|
78
|
+
}
|
|
79
|
+
return index
|
|
80
|
+
})
|
|
81
|
+
|
|
56
82
|
const getNode = (id: TreeNodeId | undefined) => (id !== undefined ? nodesMap.value.get(id) : undefined)
|
|
57
83
|
const getNodes = (ids: TreeNodeId[]) => ids.map(getNode).filter(node => node !== undefined) as TreeNode[]
|
|
58
84
|
|
|
@@ -80,46 +106,19 @@ export function useTree<
|
|
|
80
106
|
return undefined
|
|
81
107
|
}
|
|
82
108
|
|
|
83
|
-
const
|
|
84
|
-
const node = findNodeByObjectId(nodes.value,
|
|
109
|
+
const expandToNode = (objectId: string | number): TreeNode | undefined => {
|
|
110
|
+
const node = findNodeByObjectId(nodes.value, objectId)
|
|
85
111
|
if (!node) {
|
|
86
|
-
|
|
87
|
-
useTimeoutFn(async () => {
|
|
88
|
-
await nextTick()
|
|
89
|
-
await scrollToNodeElement(id, options)
|
|
90
|
-
}, 200)
|
|
91
|
-
}
|
|
92
|
-
return
|
|
112
|
+
return undefined
|
|
93
113
|
}
|
|
94
114
|
|
|
95
|
-
getHierarchy(node).forEach(
|
|
96
|
-
if (
|
|
97
|
-
|
|
115
|
+
getHierarchy(node).forEach(ancestor => {
|
|
116
|
+
if (ancestor.isBranch) {
|
|
117
|
+
ancestor.toggleCollapse(false)
|
|
98
118
|
}
|
|
99
119
|
})
|
|
100
120
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!nodeElement) {
|
|
104
|
-
useTimeoutFn(async () => {
|
|
105
|
-
await nextTick()
|
|
106
|
-
await scrollToNodeElement(id, options)
|
|
107
|
-
}, 200)
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const hasChildren = node.isBranch && node.hasChildren
|
|
112
|
-
const cfg: ScrollIntoViewOptions = options ?? { block: hasChildren ? 'start' : 'center', behavior: 'smooth' }
|
|
113
|
-
|
|
114
|
-
if (hasChildren) {
|
|
115
|
-
nodeElement.style.scrollMarginTop = '0.8rem'
|
|
116
|
-
|
|
117
|
-
useTimeoutFn(async () => {
|
|
118
|
-
nodeElement.style.scrollMarginTop = ''
|
|
119
|
-
}, 1000)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
nodeElement.scrollIntoView(cfg)
|
|
121
|
+
return node
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
const selectedNodes = computed(() => getNodes(Array.from(selectedIds.value.values())))
|
|
@@ -140,6 +139,8 @@ export function useTree<
|
|
|
140
139
|
|
|
141
140
|
return {
|
|
142
141
|
nodes: visibleNodes,
|
|
142
|
+
flatNodes,
|
|
143
|
+
flatNodeIndexById,
|
|
143
144
|
activeId,
|
|
144
145
|
activeNode,
|
|
145
146
|
selectedIds,
|
|
@@ -148,6 +149,6 @@ export function useTree<
|
|
|
148
149
|
collapsedIds,
|
|
149
150
|
expandedIds,
|
|
150
151
|
options,
|
|
151
|
-
|
|
152
|
+
expandToNode,
|
|
152
153
|
}
|
|
153
154
|
}
|
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
import { useUiStore } from '@core/stores/ui.store'
|
|
2
|
+
import { useLocalStorage } from '@vueuse/core'
|
|
2
3
|
import { defineStore } from 'pinia'
|
|
3
|
-
import { computed,
|
|
4
|
+
import { computed, watch } from 'vue'
|
|
4
5
|
|
|
5
6
|
export const usePanelStore = defineStore('panel', () => {
|
|
6
7
|
const uiStore = useUiStore()
|
|
7
|
-
const isExpanded =
|
|
8
|
-
const
|
|
9
|
-
const close = () => (isExpanded.value = false)
|
|
8
|
+
const isExpanded = useLocalStorage('panel.expanded', true)
|
|
9
|
+
const isLocked = useLocalStorage('panel.locked', true)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const cssHorizontalOffset = computed(() => (isExpanded.value ? 0 : '100%'))
|
|
12
|
+
/* Panel closes automatically when it has no content (true on small UI or when not locked) */
|
|
13
|
+
const syncsOpenStateWithSelection = computed(() => uiStore.isSmall || !isLocked.value)
|
|
14
|
+
|
|
15
|
+
function expand() {
|
|
16
|
+
isExpanded.value = true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function collapse() {
|
|
20
|
+
isExpanded.value = false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function syncWithSelection(hasSelection: boolean) {
|
|
24
|
+
if (!syncsOpenStateWithSelection.value) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
if (!hasSelection && isExpanded.value) {
|
|
28
|
+
collapse()
|
|
29
|
+
} else if (hasSelection && !isExpanded.value) {
|
|
30
|
+
expand()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toggleLock() {
|
|
35
|
+
const next = !isLocked.value
|
|
36
|
+
isLocked.value = next
|
|
37
|
+
if (next && !uiStore.isSmall && !isExpanded.value) {
|
|
38
|
+
expand()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
watch(
|
|
43
|
+
() => uiStore.isSmall,
|
|
44
|
+
isSmall => {
|
|
45
|
+
/* Expand when coming back from small to large UI, if locked and not expanded */
|
|
46
|
+
if (!isSmall && isLocked.value && !isExpanded.value) {
|
|
47
|
+
expand()
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{ immediate: true }
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
isExpanded,
|
|
55
|
+
isLocked,
|
|
56
|
+
syncsOpenStateWithSelection,
|
|
57
|
+
expand,
|
|
58
|
+
collapse,
|
|
59
|
+
toggleLock,
|
|
60
|
+
syncWithSelection,
|
|
61
|
+
cssHorizontalOffset,
|
|
62
|
+
}
|
|
12
63
|
})
|