@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.
- package/lib/components/button-group/VtsButtonGroup.vue +1 -1
- package/lib/components/columns/VtsColumns.vue +1 -1
- package/lib/components/console/VtsLayoutConsole.vue +1 -1
- package/lib/components/console/VtsRemoteConsole.vue +1 -1
- package/lib/components/layout/VtsLayoutSidebar.vue +5 -5
- package/lib/components/menu/MenuList.vue +1 -1
- package/lib/components/query-builder/VtsQueryBuilder.vue +104 -0
- package/lib/components/query-builder/VtsQueryBuilderButton.vue +52 -0
- package/lib/components/query-builder/VtsQueryBuilderFilter.vue +126 -0
- package/lib/components/query-builder/VtsQueryBuilderGroup.vue +157 -0
- package/lib/components/query-builder/VtsQueryBuilderModal.vue +62 -0
- package/lib/components/query-builder/VtsQueryBuilderRow.vue +88 -0
- package/lib/components/query-builder/VtsQueryBuilderTreeLine.vue +42 -0
- package/lib/components/quick-info-row/VtsQuickInfoRow.vue +1 -1
- package/lib/components/select/VtsSelect.vue +6 -2
- package/lib/components/stacked-bar-with-legend/VtsStackedBarWithLegend.vue +1 -1
- package/lib/components/tab/TabItem.vue +2 -2
- package/lib/components/tree/VtsTreeItem.vue +1 -1
- package/lib/components/ui/button-icon/UiButtonIcon.vue +6 -10
- package/lib/components/ui/input/UiInput.vue +19 -3
- package/lib/components/ui/modal/UiModal.vue +0 -1
- package/lib/components/ui/panel/UiPanel.vue +1 -1
- package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +4 -4
- package/lib/components/ui/quick-task-panel/UiQuickTaskPanel.vue +2 -2
- package/lib/composables/debounced-ref.composable.ts +40 -0
- package/lib/composables/pagination.composable.ts +1 -1
- package/lib/composables/tree-filter.composable.ts +16 -7
- package/lib/i18n.ts +4 -0
- package/lib/icons/fa-icons.ts +7 -1
- package/lib/locales/cs.json +75 -18
- package/lib/locales/da.json +14 -14
- package/lib/locales/de.json +80 -20
- package/lib/locales/en.json +54 -18
- package/lib/locales/es.json +35 -16
- package/lib/locales/fa.json +15 -15
- package/lib/locales/fi.json +1 -9
- package/lib/locales/fr.json +55 -19
- package/lib/locales/it.json +16 -15
- package/lib/locales/ko.json +59 -10
- package/lib/locales/nb-NO.json +1 -7
- package/lib/locales/nl.json +95 -18
- package/lib/locales/pl.json +1 -7
- package/lib/locales/pt-BR.json +10 -12
- package/lib/locales/ru.json +15 -15
- package/lib/locales/sv.json +15 -15
- package/lib/locales/uk.json +15 -15
- package/lib/locales/zh-Hans.json +884 -0
- package/lib/packages/collection/use-collection.ts +6 -6
- package/lib/packages/form-select/guess-value.ts +3 -2
- package/lib/packages/form-select/types.ts +1 -1
- package/lib/packages/form-select/use-form-select-controller.ts +9 -1
- package/lib/packages/form-select/use-form-select.ts +15 -15
- package/lib/packages/icon/DisplayIconSingle.vue +29 -2
- package/lib/packages/icon/create-icon-bindings.ts +3 -0
- package/lib/packages/icon/types.ts +2 -0
- package/lib/packages/query-builder/filter/convert-filter-to-group.ts +20 -0
- package/lib/packages/query-builder/filter/create-empty-filter.ts +11 -0
- package/lib/packages/query-builder/filter/create-query-builder-filter.ts +73 -0
- package/lib/packages/query-builder/filter/duplicate-filter.ts +11 -0
- package/lib/packages/query-builder/filter/is-filter-expression.ts +26 -0
- package/lib/packages/query-builder/filter/normalize-boolean-expression.ts +63 -0
- package/lib/packages/query-builder/filter/parsers/parse-contains-value.ts +5 -0
- package/lib/packages/query-builder/filter/parsers/parse-ends-with-value.ts +6 -0
- package/lib/packages/query-builder/filter/parsers/parse-glob-value.ts +5 -0
- package/lib/packages/query-builder/filter/parsers/parse-is-value.ts +10 -0
- package/lib/packages/query-builder/filter/parsers/parse-number-value.ts +9 -0
- package/lib/packages/query-builder/filter/parsers/parse-regex-value.ts +13 -0
- package/lib/packages/query-builder/filter/parsers/parse-starts-with-value.ts +6 -0
- package/lib/packages/query-builder/filter/render-filter.ts +11 -0
- package/lib/packages/query-builder/filter/use-filter-operator.ts +36 -0
- package/lib/packages/query-builder/filter/use-filter-property.ts +19 -0
- package/lib/packages/query-builder/filter/use-filter-value.ts +42 -0
- package/lib/packages/query-builder/filter/use-raw-filter.ts +66 -0
- package/lib/packages/query-builder/group/build-root-group.ts +20 -0
- package/lib/packages/query-builder/group/create-query-builder-group.ts +117 -0
- package/lib/packages/query-builder/group/duplicate-group.ts +13 -0
- package/lib/packages/query-builder/node/duplicate-node.ts +7 -0
- package/lib/packages/query-builder/node/get-comparison-operator.ts +18 -0
- package/lib/packages/query-builder/node/get-raw-filter.ts +15 -0
- package/lib/packages/query-builder/node/handle-comparison-node.ts +18 -0
- package/lib/packages/query-builder/node/handle-glob-pattern-node.ts +17 -0
- package/lib/packages/query-builder/node/handle-group-node.ts +22 -0
- package/lib/packages/query-builder/node/handle-node.ts +72 -0
- package/lib/packages/query-builder/node/handle-null-node.ts +11 -0
- package/lib/packages/query-builder/node/handle-property-group-node.ts +25 -0
- package/lib/packages/query-builder/node/handle-property-node.ts +94 -0
- package/lib/packages/query-builder/node/handle-regexp-node.ts +62 -0
- package/lib/packages/query-builder/node/handle-string-or-number-node.ts +48 -0
- package/lib/packages/query-builder/node/handle-truthy-property-node.ts +42 -0
- package/lib/packages/query-builder/query-builder-error.ts +7 -0
- package/lib/packages/query-builder/schema/parse-operators.ts +28 -0
- package/lib/packages/query-builder/schema/parse-values.ts +19 -0
- package/lib/packages/query-builder/schema/use-query-builder-schema.ts +25 -0
- package/lib/packages/query-builder/types.ts +119 -0
- package/lib/packages/query-builder/use-query-builder-filter.ts +39 -0
- package/lib/packages/query-builder/use-query-builder.ts +52 -0
- package/lib/stores/panel.store.ts +1 -1
- package/lib/stores/sidebar.store.ts +6 -6
- package/lib/stores/ui.store.ts +12 -8
- package/lib/tables/column-definitions/input-column.ts +2 -2
- package/lib/utils/query-builder/use-boolean-schema.ts +11 -0
- package/lib/utils/query-builder/use-number-schema.ts +20 -0
- package/lib/utils/query-builder/use-string-schema.ts +36 -0
- 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,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.
|
|
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.
|
|
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
|
-
|
|
56
|
-
|
|
55
|
+
isSmall: uiStore.isSmall,
|
|
56
|
+
isLarge: uiStore.isLarge,
|
|
57
57
|
}),
|
|
58
|
-
({
|
|
59
|
-
if (
|
|
58
|
+
({ isSmall, isLarge }) => {
|
|
59
|
+
if (isSmall) {
|
|
60
60
|
desktopState = isExpanded.value
|
|
61
61
|
} else {
|
|
62
|
-
isExpanded.value =
|
|
62
|
+
isExpanded.value = isLarge ? true : desktopState
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
{ immediate: true }
|
package/lib/stores/ui.store.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
12
|
+
medium: 1024,
|
|
13
|
+
large: 1440,
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
|
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",
|