@xen-orchestra/web-core 0.51.0 → 0.53.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/_reset.pcss +8 -1
- package/lib/components/column/VtsColumn.vue +5 -1
- package/lib/components/columns/VtsColumns.vue +17 -8
- package/lib/components/console/VtsRemoteConsole.vue +25 -19
- package/lib/components/form/VtsForm.vue +15 -0
- package/lib/components/{quick-info-column/VtsQuickInfoColumn.vue → key-value-list/VtsKeyValueList.vue} +3 -3
- package/lib/components/key-value-pair/VtsKeyValuePair.vue +48 -0
- package/lib/components/key-value-row/VtsKeyValueRow.vue +45 -0
- package/lib/components/status/VtsStatus.vue +1 -7
- package/lib/components/table/cells/VtsTagCell.vue +2 -4
- package/lib/components/tabular-key-value-list/VtsTabularKeyValueList.vue +29 -0
- package/lib/components/tabular-key-value-row/VtsTabularKeyValueRow.vue +42 -0
- package/lib/components/tag/VtsTag.vue +30 -0
- package/lib/components/ui/chip/UiChip.vue +52 -29
- package/lib/components/ui/info/UiInfo.vue +17 -6
- package/lib/components/ui/link/UiLink.vue +6 -2
- package/lib/components/ui/tag/UiTag.vue +10 -17
- package/lib/components/ui/tag/UiTertiaryTag.vue +39 -0
- package/lib/icons/action-icons.ts +1 -1
- package/lib/locales/cs.json +0 -1
- package/lib/locales/da.json +0 -1
- package/lib/locales/de.json +0 -1
- package/lib/locales/en.json +15 -1
- package/lib/locales/es.json +0 -1
- package/lib/locales/fr.json +15 -1
- package/lib/locales/nl.json +0 -1
- package/lib/locales/pt-BR.json +0 -1
- package/lib/locales/pt.json +0 -1
- package/lib/locales/ru.json +0 -1
- package/lib/locales/sk.json +0 -1
- package/lib/locales/sv.json +0 -1
- package/lib/locales/zh-Hans.json +0 -1
- package/lib/packages/form-validation/README.md +273 -0
- package/lib/packages/form-validation/custom-rules/out-of-range.rule.ts +15 -0
- package/lib/packages/form-validation/index.ts +8 -0
- package/lib/packages/form-validation/merge-validation-configs.ts +46 -0
- package/lib/packages/form-validation/types.ts +104 -0
- package/lib/packages/form-validation/use-form-validation.ts +196 -0
- package/lib/packages/modal/types.ts +1 -2
- package/lib/packages/remote-resource/define-remote-resource.ts +2 -1
- package/lib/packages/remote-resource/types.ts +1 -3
- package/lib/packages/request/define-request.ts +1 -2
- package/lib/packages/validated-form/README.md +389 -0
- package/lib/packages/validated-form/index.ts +2 -0
- package/lib/packages/validated-form/use-multi-step-validated-form.ts +203 -0
- package/lib/packages/validated-form/use-validated-form.ts +180 -0
- package/lib/utils/parse-tag.util.ts +22 -0
- package/package.json +11 -8
- package/tsconfig.json +2 -3
- package/lib/components/quick-info-row/VtsQuickInfoRow.vue +0 -59
- package/lib/components/ui/chip/ChipIcon.vue +0 -21
- package/lib/components/ui/chip/ChipRemoveIcon.vue +0 -16
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { InputWrapperMessage } from '@core/components/input-wrapper/VtsInputWrapper.vue'
|
|
2
|
+
import type { CollectionItemProperties, GetItemId } from '@core/packages/collection'
|
|
3
|
+
import { useFormBindings } from '@core/packages/form-bindings'
|
|
4
|
+
import { useFormSelect as _useFormSelect } from '@core/packages/form-select'
|
|
5
|
+
import type {
|
|
6
|
+
ExtractValue,
|
|
7
|
+
FormSelectId,
|
|
8
|
+
GetOptionLabel,
|
|
9
|
+
GetOptionValue,
|
|
10
|
+
UseFormSelectReturn,
|
|
11
|
+
} from '@core/packages/form-select/types.ts'
|
|
12
|
+
import { useFormValidation } from '@core/packages/form-validation'
|
|
13
|
+
import type { FormValidationConfig } from '@core/packages/form-validation/types.ts'
|
|
14
|
+
import type { EmptyObject, MaybeArray } from '@core/types/utility.type.ts'
|
|
15
|
+
import { toRef, type MaybeRefOrGetter, type ComputedRef, type Ref } from 'vue'
|
|
16
|
+
|
|
17
|
+
export type ModelBinding<T> = { modelValue: T; 'onUpdate:modelValue': (value: T) => void }
|
|
18
|
+
|
|
19
|
+
export type FieldMetadata = {
|
|
20
|
+
error: InputWrapperMessage | undefined
|
|
21
|
+
warning: InputWrapperMessage | undefined
|
|
22
|
+
onBlur: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toMessage(content: string | undefined, accent: 'danger' | 'warning'): InputWrapperMessage | undefined {
|
|
26
|
+
return content !== undefined ? { content, accent } : undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type UseFormSelectConfig<TSource, TCustomProperties extends CollectionItemProperties> = {
|
|
30
|
+
multiple?: MaybeRefOrGetter<boolean>
|
|
31
|
+
disabled?: MaybeRefOrGetter<boolean>
|
|
32
|
+
selectedLabel?: (count: number, labels: string[]) => string | undefined
|
|
33
|
+
placeholder?: MaybeRefOrGetter<string>
|
|
34
|
+
searchPlaceholder?: MaybeRefOrGetter<string>
|
|
35
|
+
loading?: MaybeRefOrGetter<boolean>
|
|
36
|
+
required?: MaybeRefOrGetter<boolean>
|
|
37
|
+
searchable?: MaybeRefOrGetter<boolean>
|
|
38
|
+
emptyOption?: MaybeRefOrGetter<{
|
|
39
|
+
value: unknown
|
|
40
|
+
properties?: TCustomProperties
|
|
41
|
+
label: string
|
|
42
|
+
selectedLabel?: string
|
|
43
|
+
}>
|
|
44
|
+
option?: {
|
|
45
|
+
id?: GetItemId<TSource>
|
|
46
|
+
value?:
|
|
47
|
+
| GetOptionValue<TSource, TCustomProperties>
|
|
48
|
+
| ((source: TSource, properties: TCustomProperties, index: number) => unknown)
|
|
49
|
+
properties?: (source: TSource) => TCustomProperties
|
|
50
|
+
label?: GetOptionLabel<TSource, TCustomProperties>
|
|
51
|
+
selectedLabel?: (source: TSource, properties: TCustomProperties) => string
|
|
52
|
+
disabled?: (source: TSource, properties: TCustomProperties) => boolean
|
|
53
|
+
searchableTerm?: (source: TSource, properties: TCustomProperties) => MaybeArray<string>
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useValidatedForm<TData extends Record<string, unknown>>(
|
|
58
|
+
data: TData,
|
|
59
|
+
config: FormValidationConfig<TData>
|
|
60
|
+
) {
|
|
61
|
+
const { useField: _useField, useSelect: _useSelect } = useFormBindings(data)
|
|
62
|
+
const { errors: rawErrors, warnings: rawWarnings, validate, reset, handleBlur } = useFormValidation(data, config)
|
|
63
|
+
|
|
64
|
+
const selectRegistry = new Map<FormSelectId, keyof TData>()
|
|
65
|
+
|
|
66
|
+
function fieldMetadata(key: keyof TData): () => FieldMetadata {
|
|
67
|
+
return () => ({
|
|
68
|
+
error: toMessage(rawErrors.value[key], 'danger'),
|
|
69
|
+
warning: toMessage(rawWarnings.value[key], 'warning'),
|
|
70
|
+
onBlur: () => handleBlur(key),
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function fieldMetadataWithExtras<E extends Record<string, unknown>>(
|
|
75
|
+
key: keyof TData,
|
|
76
|
+
extras: () => E
|
|
77
|
+
): () => FieldMetadata & E {
|
|
78
|
+
return () => ({
|
|
79
|
+
error: toMessage(rawErrors.value[key], 'danger'),
|
|
80
|
+
warning: toMessage(rawWarnings.value[key], 'warning'),
|
|
81
|
+
onBlur: () => handleBlur(key),
|
|
82
|
+
...extras(),
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function useField<K extends keyof TData>(key: K): ComputedRef<ModelBinding<TData[K]> & FieldMetadata>
|
|
87
|
+
function useField<K extends keyof TData, E extends Record<string, unknown>>(
|
|
88
|
+
key: K,
|
|
89
|
+
extras: () => E
|
|
90
|
+
): ComputedRef<ModelBinding<TData[K]> & FieldMetadata & E>
|
|
91
|
+
function useField<K extends keyof TData, E extends Record<string, unknown> = Record<string, unknown>>(
|
|
92
|
+
key: K,
|
|
93
|
+
extras?: () => E
|
|
94
|
+
) {
|
|
95
|
+
if (extras !== undefined) {
|
|
96
|
+
return _useField(key, fieldMetadataWithExtras(key, extras))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return _useField(key, fieldMetadata(key))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function useFormSelect<
|
|
103
|
+
TSource,
|
|
104
|
+
TCustomProperties extends CollectionItemProperties = EmptyObject,
|
|
105
|
+
TGetValue extends GetOptionValue<TSource, TCustomProperties> = undefined,
|
|
106
|
+
TMultiple extends boolean = false,
|
|
107
|
+
TEmptyValue = never,
|
|
108
|
+
$TValue = ExtractValue<TSource, TGetValue>,
|
|
109
|
+
>(
|
|
110
|
+
key: keyof TData,
|
|
111
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
112
|
+
formSelectConfig?: UseFormSelectConfig<TSource, TCustomProperties>
|
|
113
|
+
): UseFormSelectReturn<TCustomProperties, TSource, $TValue | TEmptyValue, TMultiple> {
|
|
114
|
+
const model = toRef(data, key) as Ref<unknown>
|
|
115
|
+
|
|
116
|
+
const result = (
|
|
117
|
+
_useFormSelect as unknown as (
|
|
118
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
119
|
+
config: UseFormSelectConfig<TSource, TCustomProperties> & { model: Ref<unknown> }
|
|
120
|
+
) => UseFormSelectReturn<TCustomProperties, TSource, $TValue | TEmptyValue, TMultiple>
|
|
121
|
+
)(sources, {
|
|
122
|
+
...formSelectConfig,
|
|
123
|
+
model,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
selectRegistry.set(result.id, key)
|
|
127
|
+
|
|
128
|
+
return result
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function useSelect(id: FormSelectId): ComputedRef<{ id: FormSelectId } & FieldMetadata>
|
|
132
|
+
function useSelect<E extends Record<string, unknown>>(
|
|
133
|
+
id: FormSelectId,
|
|
134
|
+
extras: () => E
|
|
135
|
+
): ComputedRef<{ id: FormSelectId } & FieldMetadata & E>
|
|
136
|
+
function useSelect<E extends Record<string, unknown>>(
|
|
137
|
+
id: FormSelectId,
|
|
138
|
+
key: keyof TData,
|
|
139
|
+
extras?: () => E
|
|
140
|
+
): ComputedRef<{ id: FormSelectId } & FieldMetadata & E>
|
|
141
|
+
function useSelect<E extends Record<string, unknown> = Record<string, unknown>>(
|
|
142
|
+
id: FormSelectId,
|
|
143
|
+
keyOrExtras?: keyof TData | (() => E),
|
|
144
|
+
extras?: () => E
|
|
145
|
+
) {
|
|
146
|
+
if (typeof keyOrExtras === 'string') {
|
|
147
|
+
const key = keyOrExtras as keyof TData
|
|
148
|
+
const metadata = extras !== undefined ? fieldMetadataWithExtras(key, extras) : fieldMetadata(key)
|
|
149
|
+
|
|
150
|
+
return _useSelect(id, metadata)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const extrasFromRegistry = keyOrExtras as (() => E) | undefined
|
|
154
|
+
const registryKey = selectRegistry.get(id)
|
|
155
|
+
|
|
156
|
+
if (registryKey !== undefined) {
|
|
157
|
+
const metadata =
|
|
158
|
+
extrasFromRegistry !== undefined
|
|
159
|
+
? fieldMetadataWithExtras(registryKey, extrasFromRegistry)
|
|
160
|
+
: fieldMetadata(registryKey)
|
|
161
|
+
|
|
162
|
+
return _useSelect(id, metadata)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (extrasFromRegistry !== undefined) {
|
|
166
|
+
return _useSelect(id, extrasFromRegistry)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return _useSelect(id)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
useField,
|
|
174
|
+
useFormSelect,
|
|
175
|
+
useSelect,
|
|
176
|
+
validate,
|
|
177
|
+
reset,
|
|
178
|
+
handleBlur,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type ParsedTag = {
|
|
2
|
+
term: string
|
|
3
|
+
label: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function parseTag(value: string): ParsedTag | null {
|
|
7
|
+
const index = value.indexOf('=')
|
|
8
|
+
|
|
9
|
+
if (index === -1) {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const term = value.slice(0, index).trim()
|
|
14
|
+
|
|
15
|
+
const label = value.slice(index + 1).trim()
|
|
16
|
+
|
|
17
|
+
if (term === '' || label === '') {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { term, label }
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xen-orchestra/web-core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.53.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"exports": {
|
|
7
7
|
"./*": {
|
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
|
18
18
|
"@fortawesome/vue-fontawesome": "^3.0.8",
|
|
19
19
|
"@novnc/novnc": "~1.5.0",
|
|
20
|
+
"@regle/core": "^1.20.2",
|
|
21
|
+
"@regle/rules": "^1.20.2",
|
|
22
|
+
"@regle/schemas": "^1.20.2",
|
|
20
23
|
"@types/d3-time-format": "^4.0.3",
|
|
21
|
-
"@vueuse/core": "^
|
|
22
|
-
"@vueuse/math": "^
|
|
23
|
-
"@vueuse/router": "^
|
|
24
|
-
"@vueuse/shared": "^
|
|
24
|
+
"@vueuse/core": "^14.0.0",
|
|
25
|
+
"@vueuse/math": "^14.0.0",
|
|
26
|
+
"@vueuse/router": "^14.0.0",
|
|
27
|
+
"@vueuse/shared": "^14.0.0",
|
|
25
28
|
"complex-matcher": "^1.1.1",
|
|
26
29
|
"d3-time-format": "^4.1.0",
|
|
27
30
|
"echarts": "^5.6.0",
|
|
@@ -38,16 +41,16 @@
|
|
|
38
41
|
"pinia": "^3.0.1",
|
|
39
42
|
"vue": "~3.5.13",
|
|
40
43
|
"vue-i18n": "^11.1.2",
|
|
41
|
-
"vue-router": "^
|
|
44
|
+
"vue-router": "^5.0.0"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
47
|
"@types/lodash-es": "^4.17.12",
|
|
45
48
|
"@types/novnc__novnc": "~1.5.0",
|
|
46
|
-
"@vue/tsconfig": "^0.
|
|
49
|
+
"@vue/tsconfig": "^0.9.0",
|
|
47
50
|
"pinia": "^3.0.1",
|
|
48
51
|
"vue": "~3.5.13",
|
|
49
52
|
"vue-i18n": "^11.1.2",
|
|
50
|
-
"vue-router": "^
|
|
53
|
+
"vue-router": "^5.0.0"
|
|
51
54
|
},
|
|
52
55
|
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/web-core",
|
|
53
56
|
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
package/tsconfig.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
|
3
|
-
"include": ["env.d.ts", "lib/**/*", "lib/**/*.vue"],
|
|
4
|
-
"exclude": ["lib/**/__tests__/*"],
|
|
3
|
+
"include": ["env.d.ts", "./lib/**/*", "./lib/**/*.vue"],
|
|
4
|
+
"exclude": ["./lib/**/__tests__/*"],
|
|
5
5
|
"compilerOptions": {
|
|
6
6
|
"noEmit": true,
|
|
7
|
-
"baseUrl": ".",
|
|
8
7
|
"paths": {
|
|
9
8
|
"@core/*": ["./lib/*"]
|
|
10
9
|
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="vts-quick-info-row" :class="{ mobile: uiStore.isSmall }">
|
|
3
|
-
<span v-tooltip class="typo-body-regular label text-ellipsis">
|
|
4
|
-
<slot name="label">
|
|
5
|
-
{{ label }}
|
|
6
|
-
</slot>
|
|
7
|
-
</span>
|
|
8
|
-
<span v-tooltip class="typo-body-regular value text-ellipsis">
|
|
9
|
-
<slot name="value">
|
|
10
|
-
{{ value }}
|
|
11
|
-
</slot>
|
|
12
|
-
</span>
|
|
13
|
-
</div>
|
|
14
|
-
</template>
|
|
15
|
-
|
|
16
|
-
<script lang="ts" setup>
|
|
17
|
-
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
18
|
-
import { useUiStore } from '@core/stores/ui.store.ts'
|
|
19
|
-
|
|
20
|
-
defineProps<{
|
|
21
|
-
label?: string
|
|
22
|
-
value?: string
|
|
23
|
-
}>()
|
|
24
|
-
|
|
25
|
-
defineSlots<{
|
|
26
|
-
label?(): any
|
|
27
|
-
value?(): any
|
|
28
|
-
}>()
|
|
29
|
-
|
|
30
|
-
const uiStore = useUiStore()
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
<style lang="postcss" scoped>
|
|
34
|
-
.vts-quick-info-row {
|
|
35
|
-
display: flex;
|
|
36
|
-
gap: 2.4rem;
|
|
37
|
-
|
|
38
|
-
&.mobile {
|
|
39
|
-
flex-direction: column;
|
|
40
|
-
gap: 0.8rem;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.label {
|
|
44
|
-
flex-shrink: 0;
|
|
45
|
-
color: var(--color-neutral-txt-secondary);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.value {
|
|
49
|
-
color: var(--color-neutral-txt-primary);
|
|
50
|
-
display: flex;
|
|
51
|
-
align-items: center;
|
|
52
|
-
gap: 0.8rem;
|
|
53
|
-
|
|
54
|
-
&:empty::before {
|
|
55
|
-
content: '-';
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
</style>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<VtsIcon :class="{ muted: disabled }" :name="icon" size="medium" class="chip-icon" />
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script lang="ts" setup>
|
|
6
|
-
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
|
7
|
-
import type { IconName } from '@core/icons'
|
|
8
|
-
|
|
9
|
-
defineProps<{
|
|
10
|
-
icon?: IconName
|
|
11
|
-
disabled?: boolean
|
|
12
|
-
}>()
|
|
13
|
-
</script>
|
|
14
|
-
|
|
15
|
-
<style lang="postcss" scoped>
|
|
16
|
-
.chip-icon {
|
|
17
|
-
&.muted {
|
|
18
|
-
color: var(--color-neutral-txt-secondary);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
</style>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<UiButtonIcon :accent="buttonAccent" icon="fa:xmark" size="small" />
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script lang="ts" setup>
|
|
6
|
-
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
7
|
-
import type { ChipAccent } from '@core/components/ui/chip/UiChip.vue'
|
|
8
|
-
import { computed } from 'vue'
|
|
9
|
-
|
|
10
|
-
const { accent } = defineProps<{
|
|
11
|
-
accent: ChipAccent
|
|
12
|
-
}>()
|
|
13
|
-
|
|
14
|
-
// FIXME: temporary fix for the 'success' accent, since it doesn't exist in the UiButtonIcon component
|
|
15
|
-
const buttonAccent = computed(() => (accent === 'info' || accent === 'success' ? 'brand' : accent))
|
|
16
|
-
</script>
|