1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-02 20:28:00 +00:00
bramw_baserow/web-frontend/modules/builder/components/elements/ElementPreview.vue
2024-03-05 08:16:41 +00:00

238 lines
6.5 KiB
Vue

<template>
<div
:key="element.id"
class="element-preview"
:class="{
'element-preview--active': isSelected,
'element-preview--parent-of-selected': isParentOfSelectedElement,
'element-preview--in-error': inError,
'element-preview--first-element': isFirstElement,
}"
tabindex="0"
@click="onSelect"
@keyup.d.stop="duplicateElement"
@keyup.delete.stop="deleteElement"
@keyup.p.stop="selectParentElement"
>
<InsertElementButton
v-show="isSelected"
class="element-preview__insert element-preview__insert--top"
@click="showAddElementModal(PLACEMENTS.BEFORE)"
/>
<ElementMenu
v-if="isSelected"
:placements="placements"
:placements-disabled="placementsDisabled"
:is-duplicating="isDuplicating"
:has-parent="!!parentElement"
@delete="deleteElement"
@move="$emit('move', $event)"
@duplicate="duplicateElement"
@select-parent="actionSelectElement({ element: parentElement })"
/>
<PageElement :element="element" :mode="mode" class="element--read-only" />
<InsertElementButton
v-show="isSelected"
class="element-preview__insert element-preview__insert--bottom"
@click="showAddElementModal(PLACEMENTS.AFTER)"
/>
<AddElementModal
ref="addElementModal"
:element-types-allowed="elementTypesAllowed"
:page="page"
/>
<i
v-if="inError"
class="element-preview__error-icon iconoir-warning-circle"
></i>
</div>
</template>
<script>
import ElementMenu from '@baserow/modules/builder/components/elements/ElementMenu'
import InsertElementButton from '@baserow/modules/builder/components/elements/InsertElementButton'
import PageElement from '@baserow/modules/builder/components/page/PageElement'
import { PLACEMENTS } from '@baserow/modules/builder/enums'
import AddElementModal from '@baserow/modules/builder/components/elements/AddElementModal'
import { notifyIf } from '@baserow/modules/core/utils/error'
import { mapActions, mapGetters } from 'vuex'
import { checkIntermediateElements } from '@baserow/modules/core/utils/dom'
export default {
name: 'ElementPreview',
components: {
AddElementModal,
ElementMenu,
InsertElementButton,
PageElement,
},
inject: ['builder', 'page', 'mode'],
props: {
element: {
type: Object,
required: true,
},
isLastElement: {
type: Boolean,
required: false,
default: false,
},
isFirstElement: {
type: Boolean,
required: false,
default: false,
},
placements: {
type: Array,
required: false,
default: () => [PLACEMENTS.BEFORE, PLACEMENTS.AFTER],
},
placementsDisabled: {
type: Array,
required: false,
default: () => [],
},
isRootElement: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
isDuplicating: false,
}
},
watch: {
/**
* Focuses the element if the element has been selected.
*/
isSelected(newValue, old) {
if (newValue && !old) {
this.$el.focus()
}
},
},
computed: {
...mapGetters({
elementSelected: 'element/getSelected',
elementAncestors: 'element/getAncestors',
}),
PLACEMENTS: () => PLACEMENTS,
elementTypesAllowed() {
return this.parentElementType?.childElementTypes || null
},
isSelected() {
return this.element.id === this.elementSelected?.id
},
selectedElementAncestorIds() {
if (!this.elementSelected) {
return []
}
return this.elementAncestors(this.page, this.elementSelected).map(
({ id }) => id
)
},
isParentOfSelectedElement() {
return this.selectedElementAncestorIds.includes(this.element.id)
},
elementType() {
return this.$registry.get('element', this.element.type)
},
parentElement() {
if (!this.element.parent_element_id) {
return null
}
return this.$store.getters['element/getElementById'](
this.page,
this.element.parent_element_id
)
},
parentElementType() {
return this.parentElement
? this.$registry.get('element', this.parentElement?.type)
: null
},
siblingElements() {
return this.$store.getters['element/getSiblings'](this.page, this.element)
},
samePlaceInContainerElements() {
return this.siblingElements.filter(
(e) => e.place_in_container === this.element.place_in_container
)
},
nextElement() {
return [...this.samePlaceInContainerElements].find((e) =>
e.order.gt(this.element.order)
)
},
inError() {
return this.elementType.isInError({
element: this.element,
builder: this.builder,
})
},
},
methods: {
...mapActions({
actionDuplicateElement: 'element/duplicate',
actionDeleteElement: 'element/delete',
actionSelectElement: 'element/select',
}),
onSelect($event) {
// Here we check that the event has been emitted for this particular element
// If we found an intermediate DOM element with the class `element-preview`
// It means it hasn't been originated by this element so we don't select it.
if (
!checkIntermediateElements(this.$el, $event.target, (el) => {
return el.classList.contains('element-preview')
})
) {
this.actionSelectElement({ element: this.element })
}
},
showAddElementModal(placement) {
this.$refs.addElementModal.show({
placeInContainer: this.element.place_in_container,
parentElementId: this.element.parent_element_id,
beforeId: this.getBeforeId(placement),
})
},
getBeforeId(placement) {
return placement === PLACEMENTS.BEFORE
? this.element.id
: this.nextElement?.id || null
},
async duplicateElement() {
this.isDuplicating = true
try {
await this.actionDuplicateElement({
page: this.page,
elementId: this.element.id,
})
} catch (error) {
notifyIf(error)
}
this.isDuplicating = false
},
async deleteElement() {
try {
await this.actionDeleteElement({
page: this.page,
elementId: this.element.id,
})
} catch (error) {
notifyIf(error)
}
},
selectParentElement() {
if (this.parentElement) {
this.actionSelectElement({ element: this.parentElement })
}
},
},
}
</script>