mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-12 16:28:06 +00:00
216 lines
7.9 KiB
Vue
216 lines
7.9 KiB
Vue
<template>
|
|
<div class="repeat-element--container">
|
|
<CollectionElementHeader
|
|
:element="element"
|
|
@filters-changed="adhocFilters = $event"
|
|
@sortings-changed="adhocSortings = $event"
|
|
@search-changed="adhocSearch = $event"
|
|
></CollectionElementHeader>
|
|
<div
|
|
:class="{
|
|
[`repeat-element--orientation-${element.orientation}`]: true,
|
|
}"
|
|
>
|
|
<!-- If we have any contents to repeat... -->
|
|
<template v-if="elementContent.length > 0">
|
|
<div
|
|
class="repeat-element__repeated-elements"
|
|
:style="repeatedElementsStyles"
|
|
>
|
|
<!-- Iterate over each content -->
|
|
<div v-for="(content, index) in elementContent" :key="content.id">
|
|
<!-- If the container has an children -->
|
|
<template v-if="children.length > 0">
|
|
<!-- Iterate over each child -->
|
|
<template v-for="child in children">
|
|
<!-- The first iteration is editable if we're in editing mode -->
|
|
<ElementPreview
|
|
v-if="index === 0 && isEditMode"
|
|
:key="`${child.id}-${index}`"
|
|
:element="child"
|
|
:application-context-additions="{
|
|
recordIndexPath: [
|
|
...applicationContext.recordIndexPath,
|
|
index,
|
|
],
|
|
}"
|
|
@move="$emit('move', $event)"
|
|
/>
|
|
<!-- Other iterations are not editable -->
|
|
<!-- Override the mode so that any children are in public mode -->
|
|
<PageElement
|
|
v-else
|
|
v-show="!isCollapsed"
|
|
:key="`${child.id}_${index}`"
|
|
:element="child"
|
|
:force-mode="isEditMode ? 'public' : mode"
|
|
:application-context-additions="{
|
|
recordIndexPath: [
|
|
...applicationContext.recordIndexPath,
|
|
index,
|
|
],
|
|
}"
|
|
:class="{
|
|
'repeat-element__preview': index > 0 && isEditMode,
|
|
}"
|
|
/>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<!-- We have contents, but the container has no children... -->
|
|
<template v-if="children.length === 0 && isEditMode">
|
|
<!-- Give the designer the chance to add child elements -->
|
|
<AddElementZone
|
|
:disabled="elementIsInError && !elementHasSourceOfData"
|
|
:tooltip="addElementErrorTooltipMessage"
|
|
@add-element="showAddElementModal"
|
|
></AddElementZone>
|
|
<AddElementModal
|
|
ref="addElementModal"
|
|
:page="elementPage"
|
|
></AddElementModal>
|
|
</template>
|
|
</template>
|
|
<!-- We have no contents to repeat -->
|
|
<template v-else>
|
|
<!-- We have no element content, but do have an adhoc refinements in public mode -->
|
|
<template v-if="(adhocSearch || adhocFilters) && !isEditMode">
|
|
<p class="repeat-element__empty-message">
|
|
{{ $t('repeatElement.emptyState') }}
|
|
</p>
|
|
</template>
|
|
<!-- If we also have no children, allow the designer to add elements -->
|
|
<template v-if="children.length === 0 && isEditMode">
|
|
<AddElementZone
|
|
:disabled="elementIsInError && !elementHasSourceOfData"
|
|
:tooltip="addElementErrorTooltipMessage"
|
|
@add-element="showAddElementModal"
|
|
></AddElementZone>
|
|
<AddElementModal
|
|
ref="addElementModal"
|
|
:page="elementPage"
|
|
></AddElementModal>
|
|
</template>
|
|
<!-- We have no contents, but we do have children in edit mode -->
|
|
<template v-else-if="isEditMode">
|
|
<div v-if="contentLoading" class="loading"></div>
|
|
<template v-else>
|
|
<ElementPreview
|
|
v-for="child in children"
|
|
:key="child.id"
|
|
:element="child"
|
|
@move="$emit('move', $event)"
|
|
/>
|
|
</template>
|
|
</template>
|
|
</template>
|
|
<div class="repeat-element__footer">
|
|
<ABButton
|
|
v-if="hasMorePage && children.length > 0"
|
|
:style="getStyleOverride('button')"
|
|
:disabled="contentLoading || !contentFetchEnabled"
|
|
:loading="contentLoading"
|
|
@click="loadMore()"
|
|
>
|
|
{{ resolvedButtonLoadMoreLabel || $t('repeatElement.showMore') }}
|
|
</ABButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapGetters } from 'vuex'
|
|
|
|
import AddElementZone from '@baserow/modules/builder/components/elements/AddElementZone'
|
|
import containerElement from '@baserow/modules/builder/mixins/containerElement'
|
|
import collectionElement from '@baserow/modules/builder/mixins/collectionElement'
|
|
import AddElementModal from '@baserow/modules/builder/components/elements/AddElementModal'
|
|
import ElementPreview from '@baserow/modules/builder/components/elements/ElementPreview'
|
|
import PageElement from '@baserow/modules/builder/components/page/PageElement'
|
|
import { ensureString } from '@baserow/modules/core/utils/validator'
|
|
import { RepeatElementType } from '@baserow/modules/builder/elementTypes'
|
|
import CollectionElementHeader from '@baserow/modules/builder/components/elements/components/CollectionElementHeader'
|
|
|
|
export default {
|
|
name: 'RepeatElement',
|
|
components: {
|
|
CollectionElementHeader,
|
|
PageElement,
|
|
ElementPreview,
|
|
AddElementModal,
|
|
AddElementZone,
|
|
},
|
|
mixins: [containerElement, collectionElement],
|
|
props: {
|
|
/**
|
|
* @type {Object}
|
|
* @property {int} data_source_id - The collection data source Id we want to display.
|
|
* @property {int} items_per_page - The number of items per page.
|
|
* @property {str} orientation - The orientation to repeat in (vertical, horizontal).
|
|
* @property {Object} items_per_row - The number of items, per device, which should
|
|
* be repeated in a row. Only applicable to when the orientation is 'horizontal'.
|
|
*/
|
|
element: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
},
|
|
computed: {
|
|
...mapGetters({ deviceTypeSelected: 'page/getDeviceTypeSelected' }),
|
|
isCollapsed() {
|
|
return this.$store.getters['element/getRepeatElementCollapsed'](
|
|
this.element
|
|
)
|
|
},
|
|
repeatElementIsNested() {
|
|
return this.elementType.hasAncestorOfType(
|
|
this.elementPage,
|
|
this.element,
|
|
RepeatElementType.getType()
|
|
)
|
|
},
|
|
addElementErrorTooltipMessage() {
|
|
if (!this.repeatElementIsNested && this.element.data_source_id === null) {
|
|
return this.$t('repeatElement.missingDataSourceTooltip')
|
|
}
|
|
return this.$t('repeatElement.missingSchemaPropertyTooltip')
|
|
},
|
|
repeatedElementsStyles() {
|
|
// These styles are applied inline as we are unable to provide
|
|
// the CSS rules with the correct `items_per_row` per device. If
|
|
// we add CSS vars to the element, and pass them into the
|
|
// `grid-template-columns` rule's `repeat`, it will cause a repaint
|
|
// following page load when the orientation is horizontal. Initially the
|
|
// page visitor will see repetitions vertically, then suddenly horizontally.
|
|
if (this.element.orientation === 'vertical') {
|
|
return {
|
|
display: 'flex',
|
|
'flex-direction': 'column',
|
|
}
|
|
} else {
|
|
return {
|
|
display: 'grid',
|
|
'grid-template-columns': `repeat(${
|
|
this.element.items_per_row[this.deviceTypeSelected]
|
|
}, 1fr)`,
|
|
}
|
|
}
|
|
},
|
|
resolvedButtonLoadMoreLabel() {
|
|
return ensureString(
|
|
this.resolveFormula(this.element.button_load_more_label)
|
|
)
|
|
},
|
|
},
|
|
methods: {
|
|
showAddElementModal() {
|
|
this.$refs.addElementModal.show({
|
|
placeInContainer: null,
|
|
parentElementId: this.element.id,
|
|
})
|
|
},
|
|
},
|
|
}
|
|
</script>
|