1
0
mirror of https://gitlab.com/bramw/baserow.git synced 2024-11-24 16:36:46 +00:00
bramw_baserow/web-frontend/modules/database/components/view/ViewFieldConditionsForm.vue
2024-10-07 07:07:41 +00:00

351 lines
9.7 KiB
Vue

<template>
<div
v-auto-overflow-scroll="scrollable"
class="filters__items"
:class="{
'filters__container--dark': variant === 'dark',
'filters__items--full-width': fullWidth,
'filters__items--scrollable': scrollable,
}"
>
<div
v-for="(filter, index) in filtersTree.filtersOrdered()"
:key="index"
class="filters__item-wrapper"
>
<div class="filters__item filters__item--level-1">
<ViewFilterFormOperator
:index="index"
:filter-type="filterType"
:disable-filter="disableFilter"
@updateFilterType="$emit('updateFilterType', { value: $event })"
/>
<ViewFieldConditionItem
:ref="`condition-${filter.id}`"
:filter="filter"
:view="view"
:is-public-view="isPublicView"
:fields="fields"
:disable-filter="disableFilter"
:read-only="readOnly"
@updateFilter="updateFilter({ filter, values: $event })"
@deleteFilter="deleteFilter({ filter, event: $event })"
>
<template #filterInputComponent="{ slotProps }">
<slot name="filterInputComponent" :slot-props="slotProps"></slot>
</template>
<template #afterValueInput="{ slotProps }">
<slot name="afterValueInput" :slot-props="slotProps"></slot>
</template>
</ViewFieldConditionItem>
</div>
</div>
<div
v-for="(groupNode, groupIndex) in filtersTree.groupsOrdered()"
:key="filtersTree.filters.length + groupIndex"
class="filters__group-item-wrapper"
>
<ViewFilterFormOperator
:index="filtersTree.filters.length + groupIndex"
:filter-type="filterType"
:disable-filter="disableFilter"
@updateFilterType="$emit('updateFilterType', { value: $event })"
/>
<ViewFieldConditionGroup
:group-node="groupNode"
:disable-filter="disableFilter"
:is-public-view="isPublicView"
:read-only="readOnly"
:fields="fields"
:view="view"
:can-add-filter-groups="canAddFilterGroups"
:add-condition-string="addConditionString"
:add-condition-group-string="addConditionGroupString"
@addFilter="$emit('addFilter', $event)"
@addFilterGroup="$emit('addFilterGroup', $event)"
@updateFilter="updateFilter($event)"
@deleteFilter="deleteFilter($event)"
@updateFilterType="$emit('updateFilterType', $event)"
>
</ViewFieldConditionGroup>
</div>
</div>
</template>
<script>
import ViewFilterFormOperator from '@baserow/modules/database/components/view/ViewFilterFormOperator'
import ViewFieldConditionItem from '@baserow/modules/database/components/view/ViewFieldConditionItem'
import ViewFieldConditionGroup from '@baserow/modules/database/components/view/ViewFieldConditionGroup'
import { sortNumbersAndUuid1Asc } from '@baserow/modules/core/utils/sort'
const GroupNode = class {
constructor(group, parent = null, sorted = false) {
this.group = group
this.parent = parent
this.children = []
this.filters = []
this.sorted = sorted
if (parent) {
parent.children.push(this)
}
}
groupsOrdered() {
if (this.sorted) {
return this.children
}
return this.children.sort((a, b) => {
return sortNumbersAndUuid1Asc(a.group, b.group)
})
}
filtersOrdered() {
if (this.sorted) {
return this.filters
}
return this.filters.sort(sortNumbersAndUuid1Asc)
}
findGroup(id) {
if (this.group && this.group.id === id) {
return this
}
for (const groupNode of this.children) {
const found = groupNode.findGroup(id)
if (found) {
return found
}
}
return null
}
addFilter(filter) {
this.filters.push(filter)
}
remove() {
if (this.parent) {
this.parent.children = this.parent.children.filter((g) => g !== this)
}
}
}
export default {
name: 'ViewFieldConditionsForm',
components: {
ViewFilterFormOperator,
ViewFieldConditionItem,
ViewFieldConditionGroup,
},
props: {
filters: {
type: Array,
required: true,
},
filterGroups: {
type: Array,
required: false,
default: () => [],
},
disableFilter: {
type: Boolean,
required: true,
},
filterType: {
type: String,
required: true,
},
fields: {
type: Array,
required: true,
},
// A view is optional, but may be required by some specific
// field components, such as `ViewFilterTypeLinkRow`.
view: {
type: Object,
required: false,
default: () => undefined,
},
isPublicView: {
type: Boolean,
required: false,
default: false,
},
readOnly: {
type: Boolean,
required: true,
},
addConditionString: {
type: String,
required: false,
default: null,
},
addConditionGroupString: {
type: String,
required: false,
default: null,
},
variant: {
type: String,
required: false,
default: 'light',
},
fullWidth: {
type: Boolean,
required: false,
default: false,
},
sorted: {
type: Boolean,
required: false,
default: false,
},
/*
* Allows the parent component to provide a function that will be called
* when a filter is updated. This is useful for components that need to
* do additional processing when a filter is updated.
*/
prepareValue: {
type: Function,
required: false,
default: (value, filter, field, filterType) => {
return filterType.prepareValue(value, field, true)
},
},
scrollable: {
type: Boolean,
required: false,
default: false,
},
canAddFilterGroups: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
groups: {},
}
},
computed: {
filterTypes() {
return this.$registry.getAll('viewFilter')
},
filtersTree() {
const root = new GroupNode(null, null, this.sorted)
const groups = { '': root }
for (const filterGroup of this.filterGroups) {
const parentId = filterGroup.parent_group || ''
const parent = groups[parentId]
const node = new GroupNode(filterGroup, parent, this.sorted)
groups[filterGroup.id] = node
}
for (const filter of this.filters) {
const groupId = filter.group != null ? filter.group : ''
const group = groups[groupId]
if (group) {
group.addFilter(filter)
}
}
return root
},
},
watch: {
'view._.focusFilter': {
handler(filterId) {
const filter =
filterId !== null && this.filters.find((f) => f.id === filterId)
if (filter) {
this.focusFilterValue(filter)
}
},
immediate: true,
},
},
methods: {
focusFilterValue(filter) {
this.$nextTick(() => {
const ref = `condition-${filter.id}`
if (this.$refs[ref] && this.$refs[ref].length > 0) {
this.$refs[ref][0]?.focusValue()
}
this.$emit('filterFocused', filter)
})
},
/**
* Returns a list of filter types that are allowed for the given fieldId.
*/
allowedFilters(filterTypes, fields, fieldId) {
const field = fields.find((f) => f.id === fieldId)
return Object.values(filterTypes).filter((filterType) => {
return field !== undefined && filterType.fieldIsCompatible(field)
})
},
deleteFilter({ filter, event }) {
event.deletedFilterEvent = true
let groupNode = this.filtersTree.findGroup(filter.group)
// Pick the outermost group that is going to be empty after the deletion, if any.
let emptyGroup = null
while (
groupNode?.group?.id &&
groupNode.filters.length + groupNode.children.length === 1
) {
emptyGroup = groupNode
groupNode = groupNode.parent
}
if (emptyGroup !== null) {
this.$emit('deleteFilterGroup', emptyGroup)
} else {
this.$emit('deleteFilter', filter)
}
},
/**
* Updates a filter with the given values. Some data manipulation will also be done
* because some filter types are not compatible with certain field types.
*/
updateFilter({ filter, values }) {
const fieldId = Object.prototype.hasOwnProperty.call(values, 'field')
? values.field
: filter.field
const type = Object.prototype.hasOwnProperty.call(values, 'type')
? values.type
: filter.type
const value = Object.prototype.hasOwnProperty.call(values, 'value')
? values.value
: filter.value
// If the field has changed we need to check if the filter type is compatible
// and if not we are going to choose the first compatible type.
if (Object.prototype.hasOwnProperty.call(values, 'field')) {
const allowedFilterTypes = this.allowedFilters(
this.filterTypes,
this.fields,
fieldId
).map((filter) => filter.type)
if (!allowedFilterTypes.includes(type)) {
values.type = allowedFilterTypes[0]
}
}
// If the type or value has changed it could be that the value needs to be
// formatted or prepared.
if (
Object.prototype.hasOwnProperty.call(values, 'field') ||
Object.prototype.hasOwnProperty.call(values, 'type') ||
Object.prototype.hasOwnProperty.call(values, 'value')
) {
const filterType = this.$registry.get('viewFilter', type)
const field = this.fields.find(({ id }) => id === fieldId)
values.value = this.prepareValue(value, filter, field, filterType)
}
this.$emit('updateFilter', { filter, values })
},
},
}
</script>