@xen-orchestra/web-core 0.43.0 → 0.45.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.
Files changed (104) hide show
  1. package/lib/components/button-group/VtsButtonGroup.vue +1 -1
  2. package/lib/components/columns/VtsColumns.vue +1 -1
  3. package/lib/components/console/VtsLayoutConsole.vue +1 -1
  4. package/lib/components/console/VtsRemoteConsole.vue +1 -1
  5. package/lib/components/layout/VtsLayoutSidebar.vue +5 -5
  6. package/lib/components/menu/MenuList.vue +1 -1
  7. package/lib/components/query-builder/VtsQueryBuilder.vue +104 -0
  8. package/lib/components/query-builder/VtsQueryBuilderButton.vue +52 -0
  9. package/lib/components/query-builder/VtsQueryBuilderFilter.vue +126 -0
  10. package/lib/components/query-builder/VtsQueryBuilderGroup.vue +157 -0
  11. package/lib/components/query-builder/VtsQueryBuilderModal.vue +62 -0
  12. package/lib/components/query-builder/VtsQueryBuilderRow.vue +88 -0
  13. package/lib/components/query-builder/VtsQueryBuilderTreeLine.vue +42 -0
  14. package/lib/components/quick-info-row/VtsQuickInfoRow.vue +1 -1
  15. package/lib/components/select/VtsSelect.vue +6 -2
  16. package/lib/components/stacked-bar-with-legend/VtsStackedBarWithLegend.vue +1 -1
  17. package/lib/components/tab/TabItem.vue +2 -2
  18. package/lib/components/tree/VtsTreeItem.vue +1 -1
  19. package/lib/components/ui/button-icon/UiButtonIcon.vue +6 -10
  20. package/lib/components/ui/input/UiInput.vue +19 -3
  21. package/lib/components/ui/modal/UiModal.vue +0 -1
  22. package/lib/components/ui/panel/UiPanel.vue +1 -1
  23. package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +4 -4
  24. package/lib/components/ui/quick-task-panel/UiQuickTaskPanel.vue +2 -2
  25. package/lib/composables/debounced-ref.composable.ts +40 -0
  26. package/lib/composables/pagination.composable.ts +1 -1
  27. package/lib/composables/tree-filter.composable.ts +16 -7
  28. package/lib/i18n.ts +4 -0
  29. package/lib/icons/fa-icons.ts +7 -1
  30. package/lib/locales/cs.json +75 -18
  31. package/lib/locales/da.json +14 -14
  32. package/lib/locales/de.json +80 -20
  33. package/lib/locales/en.json +54 -18
  34. package/lib/locales/es.json +35 -16
  35. package/lib/locales/fa.json +15 -15
  36. package/lib/locales/fi.json +1 -9
  37. package/lib/locales/fr.json +55 -19
  38. package/lib/locales/it.json +16 -15
  39. package/lib/locales/ko.json +59 -10
  40. package/lib/locales/nb-NO.json +1 -7
  41. package/lib/locales/nl.json +95 -18
  42. package/lib/locales/pl.json +1 -7
  43. package/lib/locales/pt-BR.json +10 -12
  44. package/lib/locales/ru.json +15 -15
  45. package/lib/locales/sv.json +15 -15
  46. package/lib/locales/uk.json +15 -15
  47. package/lib/locales/zh-Hans.json +884 -0
  48. package/lib/packages/collection/use-collection.ts +6 -6
  49. package/lib/packages/form-select/guess-value.ts +3 -2
  50. package/lib/packages/form-select/types.ts +1 -1
  51. package/lib/packages/form-select/use-form-select-controller.ts +9 -1
  52. package/lib/packages/form-select/use-form-select.ts +15 -15
  53. package/lib/packages/icon/DisplayIconSingle.vue +29 -2
  54. package/lib/packages/icon/create-icon-bindings.ts +3 -0
  55. package/lib/packages/icon/types.ts +2 -0
  56. package/lib/packages/query-builder/filter/convert-filter-to-group.ts +20 -0
  57. package/lib/packages/query-builder/filter/create-empty-filter.ts +11 -0
  58. package/lib/packages/query-builder/filter/create-query-builder-filter.ts +73 -0
  59. package/lib/packages/query-builder/filter/duplicate-filter.ts +11 -0
  60. package/lib/packages/query-builder/filter/is-filter-expression.ts +26 -0
  61. package/lib/packages/query-builder/filter/normalize-boolean-expression.ts +63 -0
  62. package/lib/packages/query-builder/filter/parsers/parse-contains-value.ts +5 -0
  63. package/lib/packages/query-builder/filter/parsers/parse-ends-with-value.ts +6 -0
  64. package/lib/packages/query-builder/filter/parsers/parse-glob-value.ts +5 -0
  65. package/lib/packages/query-builder/filter/parsers/parse-is-value.ts +10 -0
  66. package/lib/packages/query-builder/filter/parsers/parse-number-value.ts +9 -0
  67. package/lib/packages/query-builder/filter/parsers/parse-regex-value.ts +13 -0
  68. package/lib/packages/query-builder/filter/parsers/parse-starts-with-value.ts +6 -0
  69. package/lib/packages/query-builder/filter/render-filter.ts +11 -0
  70. package/lib/packages/query-builder/filter/use-filter-operator.ts +36 -0
  71. package/lib/packages/query-builder/filter/use-filter-property.ts +19 -0
  72. package/lib/packages/query-builder/filter/use-filter-value.ts +42 -0
  73. package/lib/packages/query-builder/filter/use-raw-filter.ts +66 -0
  74. package/lib/packages/query-builder/group/build-root-group.ts +20 -0
  75. package/lib/packages/query-builder/group/create-query-builder-group.ts +117 -0
  76. package/lib/packages/query-builder/group/duplicate-group.ts +13 -0
  77. package/lib/packages/query-builder/node/duplicate-node.ts +7 -0
  78. package/lib/packages/query-builder/node/get-comparison-operator.ts +18 -0
  79. package/lib/packages/query-builder/node/get-raw-filter.ts +15 -0
  80. package/lib/packages/query-builder/node/handle-comparison-node.ts +18 -0
  81. package/lib/packages/query-builder/node/handle-glob-pattern-node.ts +17 -0
  82. package/lib/packages/query-builder/node/handle-group-node.ts +22 -0
  83. package/lib/packages/query-builder/node/handle-node.ts +72 -0
  84. package/lib/packages/query-builder/node/handle-null-node.ts +11 -0
  85. package/lib/packages/query-builder/node/handle-property-group-node.ts +25 -0
  86. package/lib/packages/query-builder/node/handle-property-node.ts +94 -0
  87. package/lib/packages/query-builder/node/handle-regexp-node.ts +62 -0
  88. package/lib/packages/query-builder/node/handle-string-or-number-node.ts +48 -0
  89. package/lib/packages/query-builder/node/handle-truthy-property-node.ts +42 -0
  90. package/lib/packages/query-builder/query-builder-error.ts +7 -0
  91. package/lib/packages/query-builder/schema/parse-operators.ts +28 -0
  92. package/lib/packages/query-builder/schema/parse-values.ts +19 -0
  93. package/lib/packages/query-builder/schema/use-query-builder-schema.ts +25 -0
  94. package/lib/packages/query-builder/types.ts +119 -0
  95. package/lib/packages/query-builder/use-query-builder-filter.ts +39 -0
  96. package/lib/packages/query-builder/use-query-builder.ts +52 -0
  97. package/lib/stores/panel.store.ts +1 -1
  98. package/lib/stores/sidebar.store.ts +6 -6
  99. package/lib/stores/ui.store.ts +12 -8
  100. package/lib/tables/column-definitions/input-column.ts +2 -2
  101. package/lib/utils/query-builder/use-boolean-schema.ts +11 -0
  102. package/lib/utils/query-builder/use-number-schema.ts +20 -0
  103. package/lib/utils/query-builder/use-string-schema.ts +36 -0
  104. package/package.json +3 -1
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <div class="vts-query-builder-tree-line" :class="{ last }" />
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ defineProps<{
7
+ last?: boolean
8
+ }>()
9
+ </script>
10
+
11
+ <style lang="postcss" scoped>
12
+ .vts-query-builder-tree-line {
13
+ display: flex;
14
+ flex-direction: column;
15
+ margin-inline-start: 1rem;
16
+
17
+ &::before,
18
+ &::after {
19
+ content: '';
20
+ border-inline-start: 0.3rem solid #00000060;
21
+ padding-inline-start: 2rem;
22
+ }
23
+
24
+ &::before {
25
+ height: 3rem;
26
+ border-block-end: 0.3rem solid #00000060;
27
+ }
28
+
29
+ &::after {
30
+ flex: 1;
31
+ }
32
+
33
+ &.last {
34
+ &::before {
35
+ height: 2rem;
36
+ }
37
+ &::after {
38
+ border-inline-start: none;
39
+ }
40
+ }
41
+ }
42
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="vts-quick-info-row" :class="{ mobile: uiStore.isMobile }">
2
+ <div class="vts-quick-info-row" :class="{ mobile: uiStore.isSmall }">
3
3
  <span v-tooltip class="typo-body-regular label text-ellipsis">
4
4
  <slot name="label">
5
5
  {{ label }}
@@ -12,7 +12,7 @@
12
12
  readonly
13
13
  />
14
14
 
15
- <Teleport v-if="isOpen" to="body">
15
+ <template v-if="isOpen">
16
16
  <VtsBackdrop />
17
17
 
18
18
  <UiDropdownList ref="dropdownRef" :style="floatingStyles" class="dropdown-list">
@@ -36,7 +36,7 @@
36
36
  </slot>
37
37
  </template>
38
38
  </UiDropdownList>
39
- </Teleport>
39
+ </template>
40
40
  </div>
41
41
  </template>
42
42
 
@@ -95,6 +95,10 @@ const className = computed(() => toVariants({ accent }))
95
95
  const { width } = useElementSize(useCurrentElement())
96
96
 
97
97
  const minWidth = computed(() => `${width.value}px`)
98
+
99
+ defineExpose({
100
+ focus: () => triggerRef.value?.focus(),
101
+ })
98
102
  </script>
99
103
 
100
104
  <style lang="postcss" scoped>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="vts-stacked-bar-with-legend">
3
3
  <UiStackedBar :segments :max-value="maxValue" />
4
- <VtsLegendList class="list" :horizontal="!uiStore.isMobile">
4
+ <VtsLegendList class="list" :horizontal="!uiStore.isSmall">
5
5
  <UiLegend
6
6
  v-for="segment in segments"
7
7
  :key="segment.label"
@@ -20,13 +20,13 @@ const props = withDefaults(
20
20
  { tag: 'span' }
21
21
  )
22
22
 
23
- const { isMobile } = storeToRefs(useUiStore())
23
+ const { isSmall } = storeToRefs(useUiStore())
24
24
 
25
25
  const isDisabled = useDisabled(() => props.disabled)
26
26
 
27
27
  const classNames = computed(() => {
28
28
  return [
29
- isMobile.value ? 'typo-caption-small' : 'typo-caption',
29
+ isSmall.value ? 'typo-caption-small' : 'typo-caption',
30
30
  {
31
31
  disabled: isDisabled.value,
32
32
  active: props.active,
@@ -32,7 +32,7 @@ const updateHasChildren = () => {
32
32
  }
33
33
 
34
34
  const handleClick = () => {
35
- if (uiStore.isMobile) {
35
+ if (uiStore.isSmall) {
36
36
  sidebar.toggleExpand(false)
37
37
  }
38
38
  }
@@ -17,13 +17,7 @@ export type ButtonIconAccent = 'brand' | 'warning' | 'danger'
17
17
 
18
18
  export type ButtonIconSize = 'small' | 'medium' | 'large'
19
19
 
20
- const {
21
- accent,
22
- size,
23
- disabled,
24
- selected,
25
- targetScale = 1,
26
- } = defineProps<{
20
+ const { accent, size, disabled, selected, targetScale } = defineProps<{
27
21
  icon: IconName
28
22
  size: ButtonIconSize
29
23
  accent: ButtonIconAccent
@@ -34,11 +28,13 @@ const {
34
28
  }>()
35
29
 
36
30
  const cssTargetScale = computed(() => {
37
- if (typeof targetScale === 'number') {
38
- return `scale(${targetScale})`
31
+ const scale = targetScale ?? (size === 'small' ? 1.6 : 1)
32
+
33
+ if (typeof scale === 'number') {
34
+ return `scale(${scale})`
39
35
  }
40
36
 
41
- return `scale(${targetScale.x}, ${targetScale.y})`
37
+ return `scale(${scale.x}, ${scale.y})`
42
38
  })
43
39
 
44
40
  const classNames = computed(() => {
@@ -1,7 +1,12 @@
1
1
  <!-- v5 -->
2
2
  <template>
3
3
  <div :class="toVariants({ accent, disabled })" class="ui-input" @click.self="focus()">
4
+ <span v-if="readonly" class="typo-body-regular input text-ellipsis">
5
+ <input ref="inputRef" class="readonly-input" readonly type="text" />
6
+ {{ modelValue }}
7
+ </span>
4
8
  <input
9
+ v-else
5
10
  :id="wrapperController?.id ?? id"
6
11
  ref="inputRef"
7
12
  :value="modelValue"
@@ -15,7 +20,6 @@
15
20
  <UiButtonIcon
16
21
  v-if="!disabled && modelValue && clearable"
17
22
  icon="fa:xmark"
18
- :target-scale="1.6"
19
23
  accent="brand"
20
24
  size="small"
21
25
  @click="clear()"
@@ -53,13 +57,18 @@ const {
53
57
  id?: string
54
58
  required?: boolean
55
59
  disabled?: boolean
60
+ readonly?: boolean
56
61
  type?: InputType
57
62
  icon?: IconName
58
63
  rightIcon?: IconName
59
64
  clearable?: boolean
60
65
  }>()
61
66
 
62
- const modelValue = defineModel<string | number>({ required: true })
67
+ const emit = defineEmits<{
68
+ clear: []
69
+ }>()
70
+
71
+ const modelValue = defineModel<string | number | undefined>({ required: true })
63
72
 
64
73
  const slots = defineSlots<{
65
74
  'right-icon'?(): any
@@ -97,6 +106,7 @@ function focus() {
97
106
 
98
107
  function clear() {
99
108
  modelValue.value = ''
109
+ emit('clear')
100
110
  focus()
101
111
  }
102
112
 
@@ -115,7 +125,7 @@ defineExpose({ focus })
115
125
  color: var(--color-neutral-txt-primary);
116
126
  height: 4rem;
117
127
  width: 100%;
118
- min-width: 15rem;
128
+ text-align: left;
119
129
  padding-inline: 1.6rem;
120
130
 
121
131
  .right-icon {
@@ -123,6 +133,12 @@ defineExpose({ focus })
123
133
  color: var(--color-brand-txt-base);
124
134
  }
125
135
 
136
+ .readonly-input {
137
+ border: none;
138
+ width: 0;
139
+ padding: 0;
140
+ }
141
+
126
142
  .input {
127
143
  background-color: transparent;
128
144
  border: none;
@@ -85,7 +85,6 @@ const className = computed(() => toVariants({ accent }))
85
85
  padding: 3.2rem 2.4rem 2.4rem;
86
86
  gap: 2.4rem;
87
87
  border-radius: 1rem;
88
- overflow: auto;
89
88
 
90
89
  &:not(:has(.buttons)) {
91
90
  padding-bottom: 3.2rem;
@@ -1,6 +1,6 @@
1
1
  <!-- v2 -->
2
2
  <template>
3
- <div class="ui-panel" :class="{ error, 'mobile-drawer': uiStore.isMobile }">
3
+ <div class="ui-panel" :class="{ error, 'mobile-drawer': uiStore.isSmall }">
4
4
  <div v-if="slots.header" class="header">
5
5
  <slot name="header" />
6
6
  </div>
@@ -1,7 +1,7 @@
1
1
  <!-- v3 -->
2
2
  <template>
3
3
  <form class="ui-query-search-bar" @submit.prevent="emit('search', value)">
4
- <label v-if="uiStore.isDesktopLarge" :for="id" class="typo-body-regular-small label">
4
+ <label v-if="uiStore.isLarge" :for="id" class="typo-body-regular-small label">
5
5
  {{ t('query-search-bar:label') }}
6
6
  </label>
7
7
  <UiInput
@@ -9,11 +9,11 @@
9
9
  v-model="value"
10
10
  type="text"
11
11
  accent="brand"
12
- :aria-label="uiStore.isMobile ? t('query-search-bar:label') : undefined"
13
- :icon="!uiStore.isMobile ? 'fa:magnifying-glass' : undefined"
12
+ :aria-label="uiStore.isSmall ? t('query-search-bar:label') : undefined"
13
+ :icon="!uiStore.isSmall ? 'fa:magnifying-glass' : undefined"
14
14
  :placeholder="t('query-search-bar:placeholder')"
15
15
  />
16
- <template v-if="!uiStore.isMobile">
16
+ <template v-if="!uiStore.isSmall">
17
17
  <UiButton size="medium" accent="brand" variant="primary" type="submit" class="action-button">
18
18
  {{ t('action:search') }}
19
19
  </UiButton>
@@ -1,6 +1,6 @@
1
1
  <!-- WIP -->
2
2
  <template>
3
- <div :class="{ mobile: isMobile }" class="ui-quick-task-panel">
3
+ <div :class="{ mobile: isSmall }" class="ui-quick-task-panel">
4
4
  <VtsQuickTaskTabBar
5
5
  v-model="currentTab"
6
6
  :failure-count="failureTasks.length"
@@ -24,7 +24,7 @@ const props = defineProps<{
24
24
  loading?: boolean
25
25
  }>()
26
26
 
27
- const { isMobile } = useUiStore()
27
+ const { isSmall } = useUiStore()
28
28
 
29
29
  const currentTab = ref<TaskTab>('pending')
30
30
 
@@ -0,0 +1,40 @@
1
+ import { useDebounceFn } from '@vueuse/shared'
2
+ import { type Ref, type ComputedRef, ref, computed, watch } from 'vue'
3
+
4
+ export type UseDebouncedRefReturn<TValue> = {
5
+ value: Ref<TValue>
6
+ debouncedValue: ComputedRef<TValue>
7
+ isDebouncing: ComputedRef<boolean>
8
+ forceUpdate: () => void
9
+ }
10
+
11
+ export function useDebouncedRef<TValue>(initialValue: TValue, delay: number): UseDebouncedRefReturn<TValue> {
12
+ const value = ref(initialValue) as Ref<TValue>
13
+
14
+ const debouncedValue = ref(initialValue) as Ref<TValue>
15
+
16
+ const updater = useDebounceFn(() => {
17
+ debouncedValue.value = value.value
18
+ }, delay)
19
+
20
+ watch(value, newValue => {
21
+ if (!newValue) {
22
+ debouncedValue.value = newValue
23
+ } else {
24
+ updater()
25
+ }
26
+ })
27
+
28
+ const isDebouncing = computed(() => debouncedValue.value !== value.value)
29
+
30
+ function forceUpdate() {
31
+ debouncedValue.value = value.value
32
+ }
33
+
34
+ return {
35
+ value,
36
+ debouncedValue: computed(() => debouncedValue.value),
37
+ isDebouncing,
38
+ forceUpdate,
39
+ }
40
+ }
@@ -81,7 +81,7 @@ export function usePagination<T>(id: string, _records: MaybeRefOrGetter<T[]>) {
81
81
  const uiStore = useUiStore()
82
82
 
83
83
  const paginationBindings = computed<PaginationBindings>(() => ({
84
- size: (uiStore.isMobile ? 'small' : 'medium') as TablePaginationSize,
84
+ size: (uiStore.isSmall ? 'small' : 'medium') as TablePaginationSize,
85
85
  showBy: showBy.value,
86
86
  'onUpdate:showBy': (value: number) => (showBy.value = value),
87
87
  from: Math.max(0, startIndex.value + 1),
@@ -1,14 +1,23 @@
1
+ import { useDebouncedRef } from '@core/composables/debounced-ref.composable.ts'
1
2
  import type { TreeNodeBase } from '@core/packages/tree/tree-node-base'
2
- import { refDebounced } from '@vueuse/shared'
3
- import { computed, ref } from 'vue'
3
+ import { whenever } from '@vueuse/shared'
4
+ import { computed } from 'vue'
4
5
 
5
6
  export function useTreeFilter() {
6
- const filter = ref('')
7
- const debouncedFilter = refDebounced(filter, 500)
8
- const hasFilter = computed(() => filter.value.trim().length > 0)
9
- const isSearching = computed(() =>
10
- filter.value.trim().length === 0 ? false : filter.value !== debouncedFilter.value
7
+ const {
8
+ value: filter,
9
+ debouncedValue: debouncedFilter,
10
+ isDebouncing: isSearching,
11
+ forceUpdate,
12
+ } = useDebouncedRef('', 500)
13
+
14
+ whenever(
15
+ () => filter.value === '',
16
+ () => forceUpdate()
11
17
  )
18
+
19
+ const hasFilter = computed(() => filter.value.trim().length > 0)
20
+
12
21
  const predicate = (node: TreeNodeBase) =>
13
22
  hasFilter.value ? node.label.toLocaleLowerCase().includes(debouncedFilter.value.toLocaleLowerCase()) : undefined
14
23
 
package/lib/i18n.ts CHANGED
@@ -81,6 +81,10 @@ export const locales: Locales = {
81
81
  code: 'pl',
82
82
  name: 'Polski',
83
83
  },
84
+ 'zh-Hans': {
85
+ code: 'zh-Hans',
86
+ name: '中文(简体)',
87
+ },
84
88
  }
85
89
 
86
90
  export default createI18n({
@@ -42,6 +42,7 @@ import {
42
42
  faCircleXmark,
43
43
  faCity,
44
44
  faClock,
45
+ faClone,
45
46
  faClose,
46
47
  faCode,
47
48
  faComments,
@@ -84,6 +85,7 @@ import {
84
85
  faMinus,
85
86
  faMoon,
86
87
  faNetworkWired,
88
+ faObjectGroup,
87
89
  faPause,
88
90
  faPencil,
89
91
  faPlay,
@@ -98,6 +100,7 @@ import {
98
100
  faSatellite,
99
101
  faServer,
100
102
  faSliders,
103
+ faSpinner,
101
104
  faSquare,
102
105
  faSquareCaretDown,
103
106
  faStar,
@@ -149,8 +152,9 @@ export const faIcons = defineIconPack({
149
152
  'circle-user': { icon: faCircleUser },
150
153
  'circle-xmark': { icon: faCircleXmark },
151
154
  city: { icon: faCity },
152
- close: { icon: faClose },
153
155
  clock: { icon: faClock },
156
+ clone: { icon: faClone },
157
+ close: { icon: faClose },
154
158
  code: { icon: faCode },
155
159
  comments: { icon: faComments },
156
160
  copy: { icon: faCopy },
@@ -197,6 +201,7 @@ export const faIcons = defineIconPack({
197
201
  minus: { icon: faMinus },
198
202
  moon: { icon: faMoon },
199
203
  'network-wired': { icon: faNetworkWired },
204
+ 'object-group': { icon: faObjectGroup },
200
205
  pause: { icon: faPause },
201
206
  pencil: { icon: faPencil },
202
207
  play: { icon: faPlay },
@@ -210,6 +215,7 @@ export const faIcons = defineIconPack({
210
215
  satellite: { icon: faSatellite },
211
216
  server: { icon: faServer },
212
217
  sliders: { icon: faSliders },
218
+ spinner: { icon: faSpinner, spin: true },
213
219
  square: { icon: faSquare },
214
220
  'square-caret-down': { icon: faSquareCaretDown },
215
221
  'square-check': { icon: faSquareCheck },