@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,48 @@
1
+ import { createQueryBuilderFilter } from '@core/packages/query-builder/filter/create-query-builder-filter'
2
+ import { isFilterExpression } from '@core/packages/query-builder/filter/is-filter-expression.ts'
3
+ import { handleNode } from '@core/packages/query-builder/node/handle-node'
4
+ import type { QueryBuilderSchema } from '@core/packages/query-builder/types.ts'
5
+ import type { NumberOrStringNode, StringNode } from 'complex-matcher'
6
+ import { parse } from 'complex-matcher'
7
+
8
+ export function handleStringOrNumberNode({
9
+ property,
10
+ node,
11
+ negate,
12
+ schema,
13
+ }: {
14
+ property: string
15
+ node: StringNode | NumberOrStringNode
16
+ negate: boolean
17
+ schema: QueryBuilderSchema
18
+ }) {
19
+ // Special case: For "any property" filter (empty property), check if the string value
20
+ // is actually a filter expression (e.g., "boot:firmware:uefi") that should be parsed
21
+ // as a property path query instead of a literal string match
22
+ if (property === '' && isFilterExpression(node.value)) {
23
+ try {
24
+ const parsedExpression = parse(node.value)
25
+ // Recursively handle the parsed expression with appropriate negation
26
+ return handleNode({ node: parsedExpression, negate, schema })
27
+ } catch {
28
+ // If parsing fails, fall back to treating it as a literal string
29
+ }
30
+ }
31
+
32
+ // If the value is purely numeric and the property exists in the schema,
33
+ // check if it supports numeric operators (like greaterThan)
34
+ // If so, treat it as 'is' rather than 'contains' to preserve intent
35
+ const isNumericValue = /^\d+$/.test(node.value)
36
+ const propertySchema = schema[property]
37
+ const hasNumericOperators =
38
+ propertySchema && ('greaterThan' in propertySchema.operators || 'lessThan' in propertySchema.operators)
39
+
40
+ const shouldUseIsOperator = isNumericValue && hasNumericOperators
41
+
42
+ return createQueryBuilderFilter({
43
+ property,
44
+ operator: shouldUseIsOperator ? (negate ? 'isNot' : 'is') : negate ? 'doesNotContain' : 'contains',
45
+ value: node.value,
46
+ schema,
47
+ })
48
+ }
@@ -0,0 +1,42 @@
1
+ import { createQueryBuilderFilter } from '@core/packages/query-builder/filter/create-query-builder-filter'
2
+ import type { QueryBuilderSchema } from '@core/packages/query-builder/types.ts'
3
+ import type { TruthyProperty } from 'complex-matcher'
4
+
5
+ export function handleTruthyPropertyNode({
6
+ node,
7
+ negate,
8
+ schema,
9
+ propertyPrefix = '',
10
+ }: {
11
+ node: TruthyProperty
12
+ negate: boolean
13
+ schema: QueryBuilderSchema
14
+ propertyPrefix?: string
15
+ }) {
16
+ // Build the full property path including any prefix from parent Property nodes
17
+ const fullPropertyPath = propertyPrefix ? `${propertyPrefix}:${node.name}` : node.name
18
+
19
+ // Check if the full property path exists in the schema
20
+ const isPropertyInSchema = schema[fullPropertyPath] !== undefined
21
+
22
+ if (!isPropertyInSchema) {
23
+ // Convert to "any property" filter with the boolean expression as value
24
+ // prop? (truthy) -> any property contains prop:true
25
+ // !prop? (falsy) -> any property doesNotContain prop:true
26
+ const expressionValue = `${fullPropertyPath}:true`
27
+
28
+ return createQueryBuilderFilter({
29
+ property: '',
30
+ operator: negate ? 'doesNotContain' : 'contains',
31
+ value: expressionValue,
32
+ schema,
33
+ })
34
+ }
35
+
36
+ return createQueryBuilderFilter({
37
+ property: fullPropertyPath,
38
+ operator: negate ? 'isEmpty' : 'isNotEmpty',
39
+ value: '',
40
+ schema,
41
+ })
42
+ }
@@ -0,0 +1,7 @@
1
+ export class QueryBuilderError extends Error {
2
+ constructor(message: string, data?: any) {
3
+ super()
4
+
5
+ console.warn('QueryBuilder:', message, data)
6
+ }
7
+ }
@@ -0,0 +1,28 @@
1
+ import { parseValues } from '@core/packages/query-builder/schema/parse-values.ts'
2
+ import type { OperatorSchema, OperatorsDefinition, PropertyOperator } from '@core/packages/query-builder/types.ts'
3
+
4
+ export function parseOperators(operators: OperatorsDefinition): Record<string, OperatorSchema> {
5
+ return Object.fromEntries(
6
+ Object.keys(operators).flatMap(operator => {
7
+ const operatorConfig = operators[operator as PropertyOperator]
8
+
9
+ if (operatorConfig === undefined) {
10
+ return []
11
+ }
12
+
13
+ const { label, values, expectValue } = operatorConfig
14
+
15
+ return [
16
+ [
17
+ operator,
18
+ {
19
+ label,
20
+ operator: operator as PropertyOperator,
21
+ expectValue: expectValue !== false,
22
+ values: parseValues(values),
23
+ },
24
+ ],
25
+ ]
26
+ })
27
+ )
28
+ }
@@ -0,0 +1,19 @@
1
+ import type { ValueSchema } from '@core/packages/query-builder/types.ts'
2
+
3
+ export function parseValues(values: Record<string, string> | undefined): Record<string, ValueSchema> | undefined {
4
+ if (values === undefined) {
5
+ return undefined
6
+ }
7
+
8
+ return Object.fromEntries(
9
+ Object.keys(values).flatMap(value => [
10
+ [
11
+ value,
12
+ {
13
+ label: values[value],
14
+ value,
15
+ },
16
+ ],
17
+ ])
18
+ )
19
+ }
@@ -0,0 +1,25 @@
1
+ import { parseOperators } from '@core/packages/query-builder/schema/parse-operators.ts'
2
+ import type { PropertyPath, QueryBuilderSchema, QueryBuilderSchemaInput } from '@core/packages/query-builder/types.ts'
3
+
4
+ export function useQueryBuilderSchema<TSource>(schema: QueryBuilderSchemaInput<TSource>): QueryBuilderSchema {
5
+ return Object.fromEntries(
6
+ Object.keys(schema).flatMap(property => {
7
+ const config = schema[property as PropertyPath<TSource>]
8
+
9
+ if (config === undefined) {
10
+ return []
11
+ }
12
+
13
+ return [
14
+ [
15
+ property,
16
+ {
17
+ label: config.label,
18
+ property,
19
+ operators: parseOperators(config.operators),
20
+ },
21
+ ],
22
+ ]
23
+ })
24
+ )
25
+ }
@@ -0,0 +1,119 @@
1
+ import type { Reactive, Ref, ComputedRef } from 'vue'
2
+
3
+ export type GroupOperator = 'and' | 'or'
4
+
5
+ export type PropertyOperator =
6
+ | 'contains'
7
+ | 'doesNotContain'
8
+ | 'is'
9
+ | 'isNot'
10
+ | 'startsWith'
11
+ | 'doesNotStartWith'
12
+ | 'endsWith'
13
+ | 'doesNotEndWith'
14
+ | 'matchesRegex'
15
+ | 'doesNotMatchRegex'
16
+ | 'matchesGlob'
17
+ | 'doesNotMatchGlob'
18
+ | 'greaterThan'
19
+ | 'greaterThanOrEqual'
20
+ | 'lessThan'
21
+ | 'lessThanOrEqual'
22
+ | 'isEmpty'
23
+ | 'isNotEmpty'
24
+
25
+ export type OperatorsDefinition = {
26
+ [K in PropertyOperator]?:
27
+ | {
28
+ label: string
29
+ values?: Record<string, string>
30
+ expectValue?: never
31
+ }
32
+ | {
33
+ label: string
34
+ values?: never
35
+ expectValue?: boolean
36
+ }
37
+ }
38
+
39
+ export type PropertyDefinition = {
40
+ label: string
41
+ operators: OperatorsDefinition
42
+ }
43
+
44
+ export type PropertyPath<T> = T extends string | number | boolean | Date | any[]
45
+ ? never
46
+ : {
47
+ [K in keyof T & string]: `${K}` | `${K}:${PropertyPath<T[K]>}`
48
+ }[keyof T & string]
49
+
50
+ export type QueryBuilderSchemaInput<TSource> = {
51
+ '': PropertyDefinition
52
+ } & {
53
+ [K in PropertyPath<TSource>]?: PropertyDefinition
54
+ }
55
+
56
+ export type ValueSchema = {
57
+ label: string
58
+ value: string
59
+ }
60
+
61
+ export type OperatorSchema = {
62
+ label: string
63
+ operator: PropertyOperator
64
+ expectValue: boolean
65
+ values: Record<string, ValueSchema> | undefined
66
+ }
67
+
68
+ export type PropertySchema = {
69
+ label: string
70
+ property: string
71
+ operators: Record<string, OperatorSchema>
72
+ }
73
+
74
+ export type QueryBuilderSchema = Record<string, PropertySchema>
75
+
76
+ export type QueryBuilderGroup = Reactive<{
77
+ id: string
78
+ operator: Ref<GroupOperator>
79
+ children: Ref<QueryBuilderNode[]>
80
+ isGroup: true
81
+ rawFilter: ComputedRef<string>
82
+ addChildFilter: () => void
83
+ addChildGroup: () => void
84
+ duplicateChild: (childIndex: number) => void
85
+ removeChild: (index: number, keepChildren?: boolean) => void
86
+ wrapInGroup: () => void
87
+ convertChildToGroup: (childIndex: number) => void
88
+ schema: QueryBuilderSchema
89
+ }>
90
+
91
+ export type QueryBuilderFilter = Reactive<{
92
+ id: string
93
+ property: Ref<string | undefined>
94
+ operator: Ref<PropertyOperator | undefined>
95
+ value: Ref<any>
96
+ isGroup: false
97
+ rawFilter: ComputedRef<string>
98
+ propertyOptions: ComputedRef<PropertySchema[]>
99
+ operatorOptions: ComputedRef<OperatorSchema[]>
100
+ valueOptions: ComputedRef<ValueSchema[]>
101
+ isValid: ComputedRef<boolean>
102
+ valueType: ComputedRef<'input' | 'select' | 'none'>
103
+ }>
104
+
105
+ export type QueryBuilderNode = QueryBuilderGroup | QueryBuilderFilter
106
+
107
+ export type UseQueryBuilderReturn = {
108
+ rootGroup: Ref<QueryBuilderGroup>
109
+ isUsable: ComputedRef<boolean>
110
+ updateFilter: () => void
111
+ resetFilter: () => void
112
+ }
113
+
114
+ export type QueryBuilderConfig = {
115
+ property: string
116
+ operator: PropertyOperator
117
+ value: string
118
+ schema: QueryBuilderSchema
119
+ }
@@ -0,0 +1,39 @@
1
+ import { toComputed } from '@core/utils/to-computed.util.ts'
2
+ import { useRouteQuery } from '@vueuse/router'
3
+ import { parse } from 'complex-matcher'
4
+ import { computed, type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue'
5
+
6
+ export type UseQueryBuilderFilterReturn<TSource> = {
7
+ items: ComputedRef<TSource[]>
8
+ filter: Ref<string>
9
+ }
10
+
11
+ export function useQueryBuilderFilter<TSource>(
12
+ id: string,
13
+ sourcesRaw: MaybeRefOrGetter<TSource[]>,
14
+ options?: { initialFilter?: string }
15
+ ): UseQueryBuilderFilterReturn<TSource> {
16
+ const filter = useRouteQuery(`qb.${id}`, options?.initialFilter ?? '')
17
+
18
+ const sources = toComputed(sourcesRaw)
19
+
20
+ const mainNode = computed(() => {
21
+ try {
22
+ return parse(filter.value)
23
+ } catch {
24
+ return undefined
25
+ }
26
+ })
27
+
28
+ const predicate = computed(() => {
29
+ if (mainNode.value === undefined) {
30
+ return undefined
31
+ }
32
+
33
+ return mainNode.value.createPredicate()
34
+ })
35
+
36
+ const items = computed(() => (predicate.value ? sources.value.filter(predicate.value) : sources.value))
37
+
38
+ return { items, filter }
39
+ }
@@ -0,0 +1,52 @@
1
+ import { buildRootGroup } from '@core/packages/query-builder/group/build-root-group'
2
+ import { createQueryBuilderGroup } from '@core/packages/query-builder/group/create-query-builder-group'
3
+ import { handleNode } from '@core/packages/query-builder/node/handle-node.ts'
4
+ import type { QueryBuilderNode, QueryBuilderSchema, UseQueryBuilderReturn } from '@core/packages/query-builder/types.ts'
5
+ import { toComputed } from '@core/utils/to-computed.util.ts'
6
+ import { parse } from 'complex-matcher'
7
+
8
+ import { computed, ref, watch, type MaybeRefOrGetter, type Ref } from 'vue'
9
+
10
+ export function useQueryBuilder(
11
+ filter: Ref<string>,
12
+ schemaRaw: MaybeRefOrGetter<QueryBuilderSchema>
13
+ ): UseQueryBuilderReturn {
14
+ const rootNode = ref<QueryBuilderNode>()
15
+ const schema = toComputed(schemaRaw)
16
+
17
+ function generateRootNode() {
18
+ try {
19
+ const parsed = parse(filter.value)
20
+ rootNode.value = handleNode({ node: parsed, negate: false, schema: schema.value })
21
+ } catch {
22
+ return undefined
23
+ }
24
+ }
25
+
26
+ watch(filter, () => generateRootNode(), { immediate: true })
27
+
28
+ const isUsable = computed(() => rootNode.value !== undefined)
29
+
30
+ const rootGroup = computed(() => {
31
+ if (rootNode.value === undefined) {
32
+ return createQueryBuilderGroup({
33
+ operator: 'and',
34
+ children: [],
35
+ schema: schema.value,
36
+ })
37
+ }
38
+
39
+ return buildRootGroup(rootNode.value, schema.value)
40
+ })
41
+
42
+ function updateFilter() {
43
+ filter.value = rootGroup.value.rawFilter
44
+ }
45
+
46
+ return {
47
+ rootGroup,
48
+ updateFilter,
49
+ isUsable,
50
+ resetFilter: generateRootNode,
51
+ }
52
+ }
@@ -8,5 +8,5 @@ export const usePanelStore = defineStore('panel', () => {
8
8
  const open = () => (isExpanded.value = true)
9
9
  const close = () => (isExpanded.value = false)
10
10
 
11
- return { open, close, isExpanded: computed(() => !uiStore.isMobile || isExpanded.value) }
11
+ return { open, close, isExpanded: computed(() => !uiStore.isSmall || isExpanded.value) }
12
12
  })
@@ -37,7 +37,7 @@ export const useSidebarStore = defineStore('layout', () => {
37
37
  document.removeEventListener('mouseup', handleMouseUp)
38
38
  }
39
39
 
40
- const cssWidth = computed(() => (uiStore.isMobile ? '100%' : `${width.value}px`))
40
+ const cssWidth = computed(() => (uiStore.isSmall ? '100%' : `${width.value}px`))
41
41
 
42
42
  const cssOffset = computed(() => (isExpanded.value ? 0 : `-${cssWidth.value}`))
43
43
 
@@ -52,14 +52,14 @@ export const useSidebarStore = defineStore('layout', () => {
52
52
 
53
53
  watch(
54
54
  () => ({
55
- isMobile: uiStore.isMobile,
56
- isDesktopLarge: uiStore.isDesktopLarge,
55
+ isSmall: uiStore.isSmall,
56
+ isLarge: uiStore.isLarge,
57
57
  }),
58
- ({ isMobile, isDesktopLarge }) => {
59
- if (isMobile) {
58
+ ({ isSmall, isLarge }) => {
59
+ if (isSmall) {
60
60
  desktopState = isExpanded.value
61
61
  } else {
62
- isExpanded.value = isDesktopLarge ? true : desktopState
62
+ isExpanded.value = isLarge ? true : desktopState
63
63
  }
64
64
  },
65
65
  { immediate: true }
@@ -9,13 +9,15 @@ export const useUiStore = defineStore('ui', () => {
9
9
  const { store: colorMode } = useColorMode({ initialValue: 'auto' })
10
10
 
11
11
  const breakpoints = useBreakpoints({
12
- desktop: 1024,
13
- desktopLarge: 1440,
12
+ medium: 1024,
13
+ large: 1440,
14
14
  })
15
15
 
16
- const isMobile = breakpoints.smaller('desktop')
17
- const isDesktop = breakpoints.between('desktop', 'desktopLarge')
18
- const isDesktopLarge = breakpoints.greater('desktopLarge')
16
+ const isSmall = breakpoints.smaller('medium')
17
+ const isSmallOrMedium = breakpoints.smallerOrEqual('medium')
18
+ const isMedium = breakpoints.between('medium', 'large')
19
+ const isMediumOrLarge = breakpoints.greaterOrEqual('medium')
20
+ const isLarge = breakpoints.greater('large')
19
21
 
20
22
  const router = useRouter()
21
23
  const route = useRoute()
@@ -30,9 +32,11 @@ export const useUiStore = defineStore('ui', () => {
30
32
  return {
31
33
  colorMode,
32
34
  currentHostOpaqueRef,
33
- isMobile,
34
- isDesktop,
35
- isDesktopLarge,
35
+ isSmall,
36
+ isSmallOrMedium,
37
+ isMedium,
38
+ isMediumOrLarge,
39
+ isLarge,
36
40
  hasUi,
37
41
  }
38
42
  })
@@ -16,7 +16,7 @@ export const useInputColumn = defineColumn((config?: HeaderConfig & InputConfig)
16
16
  config?.headerIcon ?? (config?.type === 'number' ? 'fa:hashtag' : 'fa:align-left'),
17
17
  config?.headerLabel
18
18
  ),
19
- renderBody: (model: Ref<string | number>, inputProps?: { disabled?: boolean }) =>
19
+ renderBody: (model: Ref<string | number | undefined>, inputProps?: { disabled?: boolean }) =>
20
20
  renderBodyCell(() =>
21
21
  h(UiInput, {
22
22
  accent: 'brand',
@@ -24,7 +24,7 @@ export const useInputColumn = defineColumn((config?: HeaderConfig & InputConfig)
24
24
  type: toValue(config?.type),
25
25
  ...inputProps,
26
26
  modelValue: toValue(model),
27
- 'onUpdate:modelValue': (value: string | number) => {
27
+ 'onUpdate:modelValue': (value: string | number | undefined) => {
28
28
  model.value = value
29
29
  },
30
30
  })
@@ -0,0 +1,11 @@
1
+ import type { PropertyDefinition } from '@core/packages/query-builder/types.ts'
2
+
3
+ export function useBooleanSchema(label: string, values: { true: string; false: string }): PropertyDefinition {
4
+ return {
5
+ label,
6
+ operators: {
7
+ isEmpty: { label: values.false, expectValue: false },
8
+ isNotEmpty: { label: values.true, expectValue: false },
9
+ },
10
+ }
11
+ }
@@ -0,0 +1,20 @@
1
+ import type { PropertyDefinition } from '@core/packages/query-builder/types.ts'
2
+ import { useI18n } from 'vue-i18n'
3
+
4
+ export function useNumberSchema(label: string, values?: Record<string, string>): PropertyDefinition {
5
+ const { t } = useI18n()
6
+
7
+ return {
8
+ label,
9
+ operators: {
10
+ greaterThan: { label: '>', values },
11
+ lessThan: { label: '<', values },
12
+ greaterThanOrEqual: { label: '>=', values },
13
+ lessThanOrEqual: { label: '<=', values },
14
+ is: { label: '=', values },
15
+ isNot: { label: '!=', values },
16
+ isEmpty: { label: t('query-builder:operator:empty'), expectValue: false },
17
+ isNotEmpty: { label: t('query-builder:operator:not-empty'), expectValue: false },
18
+ },
19
+ }
20
+ }
@@ -0,0 +1,36 @@
1
+ import type { PropertyDefinition } from '@core/packages/query-builder/types.ts'
2
+ import { useI18n } from 'vue-i18n'
3
+
4
+ export function useStringSchema(label: string, values?: Record<string, string>): PropertyDefinition {
5
+ const { t } = useI18n()
6
+
7
+ if (values !== undefined) {
8
+ return {
9
+ label,
10
+ operators: {
11
+ is: { label: t('query-builder:operator:is'), values },
12
+ isNot: { label: t('query-builder:operator:is-not'), values },
13
+ isEmpty: { label: t('query-builder:operator:empty'), expectValue: false },
14
+ isNotEmpty: { label: t('query-builder:operator:not-empty'), expectValue: false },
15
+ },
16
+ }
17
+ }
18
+
19
+ return {
20
+ label,
21
+ operators: {
22
+ contains: { label: t('query-builder:operator:contains') },
23
+ doesNotContain: { label: t('query-builder:operator:not-contain') },
24
+ is: { label: t('query-builder:operator:is') },
25
+ isNot: { label: t('query-builder:operator:is-not') },
26
+ startsWith: { label: t('query-builder:operator:starts-with') },
27
+ endsWith: { label: t('query-builder:operator:ends-with') },
28
+ isEmpty: { label: t('query-builder:operator:empty'), expectValue: false },
29
+ isNotEmpty: { label: t('query-builder:operator:not-empty'), expectValue: false },
30
+ matchesRegex: { label: t('query-builder:operator:matches-regex') },
31
+ doesNotMatchRegex: { label: t('query-builder:operator:not-match-regex') },
32
+ matchesGlob: { label: t('query-builder:operator:matches-glob') },
33
+ doesNotMatchGlob: { label: t('query-builder:operator:not-match-glob') },
34
+ },
35
+ }
36
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xen-orchestra/web-core",
3
3
  "type": "module",
4
- "version": "0.43.0",
4
+ "version": "0.45.0",
5
5
  "private": false,
6
6
  "exports": {
7
7
  "./*": {
@@ -20,7 +20,9 @@
20
20
  "@types/d3-time-format": "^4.0.3",
21
21
  "@vueuse/core": "^13.0.0",
22
22
  "@vueuse/math": "^13.0.0",
23
+ "@vueuse/router": "^13.0.0",
23
24
  "@vueuse/shared": "^13.0.0",
25
+ "complex-matcher": "^1.1.0",
24
26
  "d3-time-format": "^4.1.0",
25
27
  "echarts": "^5.6.0",
26
28
  "human-format": "^1.2.1",