@xen-orchestra/web-core 0.42.0 → 0.44.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/banner/VtsBanner.vue +50 -0
- 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/input-wrapper/VtsInputWrapper.vue +1 -0
- 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/checkbox/UiCheckbox.vue +1 -1
- package/lib/components/ui/head-bar/UiHeadBar.vue +1 -0
- 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/components/ui/radio-button/UiRadioButton.vue +1 -1
- package/lib/components/ui/radio-button-group/UiRadioButtonGroup.vue +16 -3
- package/lib/components/ui/rich-radio-button/UiRichRadioButton.vue +247 -0
- 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/layouts/CoreLayout.vue +26 -0
- package/lib/locales/cs.json +74 -17
- package/lib/locales/da.json +14 -14
- package/lib/locales/de.json +79 -19
- package/lib/locales/en.json +46 -17
- package/lib/locales/es.json +34 -15
- package/lib/locales/fa.json +14 -14
- package/lib/locales/fi.json +0 -8
- package/lib/locales/fr.json +47 -18
- package/lib/locales/it.json +15 -14
- package/lib/locales/ko.json +59 -10
- package/lib/locales/nb-NO.json +0 -6
- package/lib/locales/nl.json +94 -17
- package/lib/locales/pl.json +0 -6
- package/lib/locales/pt-BR.json +10 -12
- package/lib/locales/ru.json +14 -14
- package/lib/locales/sv.json +14 -14
- package/lib/locales/uk.json +14 -14
- 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/packages/remote-resource/define-remote-resource.ts +11 -9
- package/lib/packages/remote-resource/sse.store.ts +31 -3
- 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,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="className" class="vts-banner">
|
|
3
|
+
<slot />
|
|
4
|
+
<div v-if="slots.addons">
|
|
5
|
+
<slot name="addons" />
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { toVariants } from '@core/utils/to-variants.util.ts'
|
|
12
|
+
import { computed } from 'vue'
|
|
13
|
+
|
|
14
|
+
export type BannerAccent = 'brand' | 'warning' | 'danger'
|
|
15
|
+
|
|
16
|
+
const { accent } = defineProps<{
|
|
17
|
+
accent: BannerAccent
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const slots = defineSlots<{
|
|
21
|
+
default(): any
|
|
22
|
+
addons?(): any
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const className = computed(() => toVariants({ accent }))
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<style scoped lang="postcss">
|
|
29
|
+
.vts-banner {
|
|
30
|
+
display: flex;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: 1.6rem;
|
|
34
|
+
padding: 0.8rem 1.6rem;
|
|
35
|
+
border-block-end: 0.1rem solid var(--color-neutral-border);
|
|
36
|
+
|
|
37
|
+
/* ACCENT */
|
|
38
|
+
&.accent--brand {
|
|
39
|
+
background-color: var(--color-brand-background-selected);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&.accent--warning {
|
|
43
|
+
background-color: var(--color-warning-background-selected);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&.accent--danger {
|
|
47
|
+
background-color: var(--color-danger-background-selected);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="uiStore.
|
|
2
|
+
<div :class="uiStore.isSmall ? 'mobile' : undefined" class="vts-remote-console">
|
|
3
3
|
<VtsStateHero v-if="!isReady" format="page" type="busy" size="medium">{{ t('loading') }}</VtsStateHero>
|
|
4
4
|
<div ref="console-container" class="console" />
|
|
5
5
|
</div>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
:class="{
|
|
4
|
-
locked: sidebar.isLocked && !ui.
|
|
4
|
+
locked: sidebar.isLocked && !ui.isSmall,
|
|
5
5
|
expanded: sidebar.isExpanded,
|
|
6
|
-
mobile: ui.
|
|
7
|
-
'border-right': !ui.
|
|
6
|
+
mobile: ui.isSmall,
|
|
7
|
+
'border-right': !ui.isSmall,
|
|
8
8
|
}"
|
|
9
9
|
class="vts-layout-sidebar"
|
|
10
10
|
>
|
|
11
|
-
<div v-if="!ui.
|
|
11
|
+
<div v-if="!ui.isSmall" class="lock">
|
|
12
12
|
<UiButtonIcon
|
|
13
13
|
v-tooltip="{
|
|
14
14
|
content: sidebar.isLocked ? t('action:sidebar-unlock') : t('action:sidebar-lock'),
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<slot name="footer" />
|
|
31
31
|
</div>
|
|
32
32
|
<div
|
|
33
|
-
v-if="!ui.
|
|
33
|
+
v-if="!ui.isSmall"
|
|
34
34
|
:class="{ active: sidebar.isResizing }"
|
|
35
35
|
class="resize-handle"
|
|
36
36
|
@mousedown="sidebar.startResize"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<form class="vts-query-builder" @submit.prevent="handleSubmit()">
|
|
3
|
+
<label :for="id" class="typo-body-regular-small label">
|
|
4
|
+
{{ t('query-builder:label') }}
|
|
5
|
+
</label>
|
|
6
|
+
<div class="input-container">
|
|
7
|
+
<UiInput
|
|
8
|
+
v-model="localFilter"
|
|
9
|
+
:accent="isUsable ? 'brand' : 'danger'"
|
|
10
|
+
:aria-label="uiStore.isSmall ? t('query-builder:label') : undefined"
|
|
11
|
+
:placeholder="t('query-builder:placeholder')"
|
|
12
|
+
clearable
|
|
13
|
+
@clear="filter = localFilter"
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<template v-if="uiStore.isSmallOrMedium">
|
|
18
|
+
<UiButtonIcon icon="fa:magnifying-glass" size="medium" accent="brand" />
|
|
19
|
+
<VtsQueryBuilderButton
|
|
20
|
+
v-model="rootGroup"
|
|
21
|
+
small
|
|
22
|
+
:disabled="!isUsable"
|
|
23
|
+
@confirm="updateFilter()"
|
|
24
|
+
@cancel="resetFilter"
|
|
25
|
+
/>
|
|
26
|
+
</template>
|
|
27
|
+
<template v-else>
|
|
28
|
+
<UiButton size="medium" accent="brand" variant="secondary" type="submit">
|
|
29
|
+
{{ t('action:search') }}
|
|
30
|
+
</UiButton>
|
|
31
|
+
|
|
32
|
+
<VtsDivider type="stretch" />
|
|
33
|
+
|
|
34
|
+
<VtsQueryBuilderButton
|
|
35
|
+
v-model="rootGroup"
|
|
36
|
+
:disabled="!isUsable"
|
|
37
|
+
@confirm="updateFilter()"
|
|
38
|
+
@cancel="resetFilter"
|
|
39
|
+
/>
|
|
40
|
+
</template>
|
|
41
|
+
</form>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script setup lang="ts">
|
|
45
|
+
import VtsDivider from '@core/components/divider/VtsDivider.vue'
|
|
46
|
+
import VtsQueryBuilderButton from '@core/components/query-builder/VtsQueryBuilderButton.vue'
|
|
47
|
+
import UiButton from '@core/components/ui/button/UiButton.vue'
|
|
48
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
49
|
+
import UiInput from '@core/components/ui/input/UiInput.vue'
|
|
50
|
+
import type { QueryBuilderSchema } from '@core/packages/query-builder/types'
|
|
51
|
+
import { useQueryBuilder } from '@core/packages/query-builder/use-query-builder'
|
|
52
|
+
import { useUiStore } from '@core/stores/ui.store'
|
|
53
|
+
import { onMounted, ref, useId, watch } from 'vue'
|
|
54
|
+
import { useI18n } from 'vue-i18n'
|
|
55
|
+
|
|
56
|
+
const { schema } = defineProps<{
|
|
57
|
+
schema: QueryBuilderSchema
|
|
58
|
+
}>()
|
|
59
|
+
|
|
60
|
+
const filter = defineModel<string>({ required: true })
|
|
61
|
+
|
|
62
|
+
const localFilter = ref('')
|
|
63
|
+
|
|
64
|
+
onMounted(() => {
|
|
65
|
+
localFilter.value = filter.value
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const { t } = useI18n()
|
|
69
|
+
|
|
70
|
+
const uiStore = useUiStore()
|
|
71
|
+
|
|
72
|
+
const id = useId()
|
|
73
|
+
|
|
74
|
+
const { rootGroup, isUsable, updateFilter, resetFilter } = useQueryBuilder(filter, () => schema)
|
|
75
|
+
|
|
76
|
+
function handleSubmit() {
|
|
77
|
+
filter.value = localFilter.value
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
watch(filter, newFilter => {
|
|
81
|
+
localFilter.value = newFilter
|
|
82
|
+
})
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<style lang="postcss" scoped>
|
|
86
|
+
.vts-query-builder {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 1.6rem;
|
|
90
|
+
|
|
91
|
+
@media (--small-or-medium) {
|
|
92
|
+
gap: 1rem;
|
|
93
|
+
|
|
94
|
+
.label {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.input-container {
|
|
100
|
+
flex: 1;
|
|
101
|
+
min-width: 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiButtonIcon
|
|
3
|
+
v-if="small"
|
|
4
|
+
v-tooltip="tooltip"
|
|
5
|
+
icon="fa:filter"
|
|
6
|
+
size="medium"
|
|
7
|
+
accent="brand"
|
|
8
|
+
:disabled
|
|
9
|
+
@click="openModal()"
|
|
10
|
+
/>
|
|
11
|
+
<UiButton v-else v-tooltip="tooltip" :disabled accent="brand" variant="tertiary" size="medium" @click="openModal()">
|
|
12
|
+
{{ t('action:use-query-builder') }}
|
|
13
|
+
</UiButton>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import UiButton from '@core/components/ui/button/UiButton.vue'
|
|
18
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
19
|
+
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
20
|
+
import { useModal } from '@core/packages/modal/use-modal'
|
|
21
|
+
import type { QueryBuilderGroup } from '@core/packages/query-builder/types'
|
|
22
|
+
import { computed } from 'vue'
|
|
23
|
+
import { useI18n } from 'vue-i18n'
|
|
24
|
+
|
|
25
|
+
const { disabled } = defineProps<{
|
|
26
|
+
disabled?: boolean
|
|
27
|
+
small?: boolean
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
confirm: []
|
|
32
|
+
cancel: []
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const rootGroup = defineModel<QueryBuilderGroup>({ required: true })
|
|
36
|
+
|
|
37
|
+
const tooltip = computed(() => (disabled ? 'Current filter is invalid or too complex' : false))
|
|
38
|
+
|
|
39
|
+
const { t } = useI18n()
|
|
40
|
+
|
|
41
|
+
const openModal = useModal(() => ({
|
|
42
|
+
component: import('@core/components/query-builder/VtsQueryBuilderModal.vue'),
|
|
43
|
+
props: {
|
|
44
|
+
modelValue: rootGroup,
|
|
45
|
+
'onUpdate:modelValue': (newRootGroup: QueryBuilderGroup) => {
|
|
46
|
+
rootGroup.value = newRootGroup
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
onConfirm: () => emit('confirm'),
|
|
50
|
+
onCancel: () => emit('cancel'),
|
|
51
|
+
}))
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vts-query-builder-filter">
|
|
3
|
+
<VtsSelect :id="propertySelectId" ref="propertySelector" accent="brand" />
|
|
4
|
+
<VtsSelect :id="operatorSelectId" accent="brand" />
|
|
5
|
+
<UiInput v-if="valueType === 'input'" v-model="value" class="value" accent="brand" />
|
|
6
|
+
<VtsSelect v-else-if="valueType === 'select'" :id="valueSelectId" class="value" accent="brand" />
|
|
7
|
+
<div>
|
|
8
|
+
<MenuList :placement="uiStore.isSmall ? 'bottom' : 'bottom-end'">
|
|
9
|
+
<template #trigger="{ open }">
|
|
10
|
+
<UiButton
|
|
11
|
+
v-if="uiStore.isSmall"
|
|
12
|
+
class="actions-button"
|
|
13
|
+
variant="secondary"
|
|
14
|
+
icon="fa:ellipsis"
|
|
15
|
+
size="medium"
|
|
16
|
+
accent="brand"
|
|
17
|
+
@click="open"
|
|
18
|
+
>
|
|
19
|
+
{{ t('filter-actions') }}
|
|
20
|
+
</UiButton>
|
|
21
|
+
<UiButtonIcon v-else icon="fa:ellipsis" size="medium" accent="brand" @click="open" />
|
|
22
|
+
</template>
|
|
23
|
+
<MenuItem icon="fa:clone" @click="emit('duplicate')">{{ t('action:duplicate') }}</MenuItem>
|
|
24
|
+
<MenuItem icon="fa:layer-group" @click="emit('convertToGroup')">{{ t('action:turn-into-group') }}</MenuItem>
|
|
25
|
+
<MenuItem icon="fa:trash" @click="emit('remove')">{{ t('action:delete-filter') }}</MenuItem>
|
|
26
|
+
</MenuList>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script lang="ts" setup>
|
|
32
|
+
import MenuItem from '@core/components/menu/MenuItem.vue'
|
|
33
|
+
import MenuList from '@core/components/menu/MenuList.vue'
|
|
34
|
+
import VtsSelect from '@core/components/select/VtsSelect.vue'
|
|
35
|
+
import UiButton from '@core/components/ui/button/UiButton.vue'
|
|
36
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
37
|
+
import UiInput from '@core/components/ui/input/UiInput.vue'
|
|
38
|
+
import { useFormSelect } from '@core/packages/form-select'
|
|
39
|
+
import type { PropertySchema, OperatorSchema, ValueSchema } from '@core/packages/query-builder/types'
|
|
40
|
+
import { useUiStore } from '@core/stores/ui.store.ts'
|
|
41
|
+
import { onMounted, useTemplateRef } from 'vue'
|
|
42
|
+
import { useI18n } from 'vue-i18n'
|
|
43
|
+
|
|
44
|
+
const { properties, operators, values, valueType } = defineProps<{
|
|
45
|
+
properties: PropertySchema[]
|
|
46
|
+
operators: OperatorSchema[]
|
|
47
|
+
values: ValueSchema[]
|
|
48
|
+
valueType: 'input' | 'select' | 'none'
|
|
49
|
+
}>()
|
|
50
|
+
|
|
51
|
+
const emit = defineEmits<{
|
|
52
|
+
remove: []
|
|
53
|
+
duplicate: []
|
|
54
|
+
convertToGroup: []
|
|
55
|
+
}>()
|
|
56
|
+
|
|
57
|
+
const property = defineModel<string | undefined>('property', { required: true })
|
|
58
|
+
|
|
59
|
+
const operator = defineModel<string | undefined>('operator', { required: true })
|
|
60
|
+
|
|
61
|
+
const value = defineModel<string | undefined>('value', { required: true })
|
|
62
|
+
|
|
63
|
+
const { t } = useI18n()
|
|
64
|
+
|
|
65
|
+
const uiStore = useUiStore()
|
|
66
|
+
|
|
67
|
+
const propertySelector = useTemplateRef('propertySelector')
|
|
68
|
+
|
|
69
|
+
const { id: propertySelectId } = useFormSelect(() => properties, {
|
|
70
|
+
model: property,
|
|
71
|
+
option: {
|
|
72
|
+
id: 'property',
|
|
73
|
+
value: 'property',
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const { id: operatorSelectId } = useFormSelect(() => operators, {
|
|
78
|
+
model: operator,
|
|
79
|
+
option: {
|
|
80
|
+
id: 'operator',
|
|
81
|
+
value: 'operator',
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const { id: valueSelectId } = useFormSelect(() => values, {
|
|
86
|
+
model: value,
|
|
87
|
+
option: {
|
|
88
|
+
id: 'value',
|
|
89
|
+
value: 'value',
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
onMounted(() => propertySelector.value?.focus())
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<style lang="postcss" scoped>
|
|
97
|
+
.vts-query-builder-filter {
|
|
98
|
+
display: flex;
|
|
99
|
+
gap: 0.5rem;
|
|
100
|
+
align-items: center;
|
|
101
|
+
background-color: #00000010;
|
|
102
|
+
padding: 0.3rem;
|
|
103
|
+
margin-inline-end: 0.5rem;
|
|
104
|
+
max-width: 100%;
|
|
105
|
+
overflow: auto;
|
|
106
|
+
border-radius: 0.3rem;
|
|
107
|
+
|
|
108
|
+
@media (--small) {
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
align-items: stretch;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.label {
|
|
114
|
+
white-space: nowrap;
|
|
115
|
+
margin-inline-end: 1rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.value {
|
|
119
|
+
flex: 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.actions-button {
|
|
123
|
+
width: 100%;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
</style>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiCard class="vts-query-builder-group" :class="[{ root, highlighted }, item.operator]">
|
|
3
|
+
<div class="group">
|
|
4
|
+
<div class="header">
|
|
5
|
+
<VtsSelect :id="groupOperatorSelectId" class="group-operator" accent="brand" />
|
|
6
|
+
<MenuList v-if="!root" placement="bottom-end">
|
|
7
|
+
<template #trigger="{ open }">
|
|
8
|
+
<UiButtonIcon size="medium" accent="brand" icon="fa:ellipsis" @click="open" />
|
|
9
|
+
</template>
|
|
10
|
+
<MenuItem @click="emit('duplicate')">{{ t('action:duplicate') }}</MenuItem>
|
|
11
|
+
<MenuItem @click="emit('remove', true)">{{ t('action:move-filters-to-parent-group') }}</MenuItem>
|
|
12
|
+
<MenuItem @click="emit('remove')">{{ t('action:delete-group') }}</MenuItem>
|
|
13
|
+
</MenuList>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="content">
|
|
16
|
+
<VtsQueryBuilderRow
|
|
17
|
+
v-for="(child, index) of item.children"
|
|
18
|
+
:key="child.id"
|
|
19
|
+
v-model:node="item.children[index]"
|
|
20
|
+
v-model:operator="item.operator"
|
|
21
|
+
:index
|
|
22
|
+
@convert-to-group="item.convertChildToGroup(index)"
|
|
23
|
+
@remove="keepChildren => item.removeChild(index, keepChildren)"
|
|
24
|
+
@duplicate="item.duplicateChild(index)"
|
|
25
|
+
/>
|
|
26
|
+
<div class="add-child-buttons">
|
|
27
|
+
<VtsQueryBuilderTreeLine last />
|
|
28
|
+
<div class="buttons">
|
|
29
|
+
<UiButton variant="secondary" size="small" accent="brand" icon="fa:ellipsis" @click="item.addChildFilter()">
|
|
30
|
+
{{ t('action:add-filter') }}
|
|
31
|
+
</UiButton>
|
|
32
|
+
<UiButton variant="secondary" size="small" accent="brand" icon="fa:ellipsis" @click="item.addChildGroup()">
|
|
33
|
+
{{ t('action:add-group') }}
|
|
34
|
+
</UiButton>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div v-if="root && item.children.length > 1" class="group-filters-button">
|
|
40
|
+
<UiButton variant="secondary" size="small" accent="brand" icon="fa:ellipsis" @click="item.wrapInGroup()">
|
|
41
|
+
{{ item.operator === 'and' ? t('action:plus-or') : t('action:plus-and') }}
|
|
42
|
+
</UiButton>
|
|
43
|
+
</div>
|
|
44
|
+
</UiCard>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script lang="ts" setup>
|
|
48
|
+
import MenuItem from '@core/components/menu/MenuItem.vue'
|
|
49
|
+
import MenuList from '@core/components/menu/MenuList.vue'
|
|
50
|
+
import VtsQueryBuilderRow from '@core/components/query-builder/VtsQueryBuilderRow.vue'
|
|
51
|
+
import VtsQueryBuilderTreeLine from '@core/components/query-builder/VtsQueryBuilderTreeLine.vue'
|
|
52
|
+
import VtsSelect from '@core/components/select/VtsSelect.vue'
|
|
53
|
+
import UiButton from '@core/components/ui/button/UiButton.vue'
|
|
54
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
55
|
+
import UiCard from '@core/components/ui/card/UiCard.vue'
|
|
56
|
+
import { useFormSelect } from '@core/packages/form-select'
|
|
57
|
+
import type { QueryBuilderGroup } from '@core/packages/query-builder/types'
|
|
58
|
+
import { computed } from 'vue'
|
|
59
|
+
import { useI18n } from 'vue-i18n'
|
|
60
|
+
|
|
61
|
+
defineProps<{
|
|
62
|
+
root?: boolean
|
|
63
|
+
highlighted?: boolean
|
|
64
|
+
}>()
|
|
65
|
+
|
|
66
|
+
const emit = defineEmits<{
|
|
67
|
+
duplicate: []
|
|
68
|
+
remove: [keepChildren?: boolean]
|
|
69
|
+
}>()
|
|
70
|
+
|
|
71
|
+
const item = defineModel<QueryBuilderGroup>({ required: true })
|
|
72
|
+
|
|
73
|
+
const { t } = useI18n()
|
|
74
|
+
|
|
75
|
+
const { id: groupOperatorSelectId } = useFormSelect(['and', 'or'], {
|
|
76
|
+
model: computed({
|
|
77
|
+
get: () => item.value.operator,
|
|
78
|
+
set: value => {
|
|
79
|
+
item.value.operator = value
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
option: {
|
|
83
|
+
label: (option: string) => (option === 'and' ? t('and') : t('or')),
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<style lang="postcss" scoped>
|
|
89
|
+
.vts-query-builder-group {
|
|
90
|
+
padding: 0;
|
|
91
|
+
border: none;
|
|
92
|
+
|
|
93
|
+
&.root {
|
|
94
|
+
margin-block-end: 1.4rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
&.and {
|
|
98
|
+
--main-color: var(--color-brand-item-active);
|
|
99
|
+
--secondary-color: var(--color-brand-background-selected);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&.or {
|
|
103
|
+
--main-color: var(--color-success-item-active);
|
|
104
|
+
--secondary-color: var(--color-success-background-selected);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.group-operator:deep(.ui-input) {
|
|
108
|
+
padding-inline: 0.6rem;
|
|
109
|
+
gap: 0.6rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.group {
|
|
113
|
+
position: relative;
|
|
114
|
+
border: 0.1rem solid var(--main-color);
|
|
115
|
+
background-color: var(--secondary-color);
|
|
116
|
+
|
|
117
|
+
.header {
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
justify-content: space-between;
|
|
121
|
+
gap: 1rem;
|
|
122
|
+
padding: 0.3rem;
|
|
123
|
+
background-color: var(--main-color);
|
|
124
|
+
position: sticky;
|
|
125
|
+
top: 0;
|
|
126
|
+
z-index: 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.content {
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
padding-block-end: 0.5rem;
|
|
133
|
+
padding-inline-start: 0.6rem;
|
|
134
|
+
max-width: 100%;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.add-child-buttons {
|
|
139
|
+
display: flex;
|
|
140
|
+
|
|
141
|
+
& > .buttons {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 0.8rem;
|
|
145
|
+
background-color: #00000010;
|
|
146
|
+
padding: 0.3rem;
|
|
147
|
+
border-radius: 0.3rem;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.group-filters-button {
|
|
152
|
+
position: absolute;
|
|
153
|
+
inset-block-end: -1.4rem;
|
|
154
|
+
inset-inline-end: 0.5rem;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<VtsModal class="vts-query-builder-modal" accent="info" dismissible>
|
|
3
|
+
<template v-if="uiStore.isSmall" #title>{{ t('query-builder') }}</template>
|
|
4
|
+
<template #content>
|
|
5
|
+
<VtsQueryBuilderGroup v-model="rootGroup" root />
|
|
6
|
+
</template>
|
|
7
|
+
<template #buttons>
|
|
8
|
+
<VtsModalCancelButton />
|
|
9
|
+
<VtsModalConfirmButton>{{ t('action:save') }}</VtsModalConfirmButton>
|
|
10
|
+
</template>
|
|
11
|
+
</VtsModal>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import VtsModal from '@core/components/modal/VtsModal.vue'
|
|
16
|
+
import VtsModalCancelButton from '@core/components/modal/VtsModalCancelButton.vue'
|
|
17
|
+
import VtsModalConfirmButton from '@core/components/modal/VtsModalConfirmButton.vue'
|
|
18
|
+
import VtsQueryBuilderGroup from '@core/components/query-builder/VtsQueryBuilderGroup.vue'
|
|
19
|
+
import { type QueryBuilderGroup } from '@core/packages/query-builder/types'
|
|
20
|
+
import { useUiStore } from '@core/stores/ui.store.ts'
|
|
21
|
+
import { useI18n } from 'vue-i18n'
|
|
22
|
+
|
|
23
|
+
const rootGroup = defineModel<QueryBuilderGroup>({ required: true })
|
|
24
|
+
|
|
25
|
+
const { t } = useI18n()
|
|
26
|
+
|
|
27
|
+
const uiStore = useUiStore()
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<style lang="postcss" scoped>
|
|
31
|
+
.vts-query-builder-modal {
|
|
32
|
+
@media (--small) {
|
|
33
|
+
&:deep(.modal) {
|
|
34
|
+
position: absolute;
|
|
35
|
+
inset: 0;
|
|
36
|
+
border-radius: 0;
|
|
37
|
+
max-width: 100vw;
|
|
38
|
+
max-height: 100vh;
|
|
39
|
+
|
|
40
|
+
& > .buttons .line {
|
|
41
|
+
flex-direction: row;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:deep(.modal) {
|
|
47
|
+
background-color: var(--color-neutral-background-primary) !important;
|
|
48
|
+
padding: 0;
|
|
49
|
+
gap: 0;
|
|
50
|
+
|
|
51
|
+
& > .main {
|
|
52
|
+
margin: 1.6rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
& > .buttons {
|
|
56
|
+
padding: 1.6rem;
|
|
57
|
+
border-top: 0.1rem solid var(--color-neutral-border);
|
|
58
|
+
align-items: flex-end;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
</style>
|