<template> <div class="page-preview__wrapper" @click.self="actionSelectElement({ element: null })" > <PreviewNavigationBar :page="page" :style="{ maxWidth }" /> <div ref="preview" class="page-preview" :style="{ 'max-width': maxWidth }"> <div ref="previewScaled" class="page-preview__scaled"> <ElementPreview v-for="(element, index) in elements" :key="element.id" is-root-element :element="element" :is-first-element="index === 0" :is-last-element="index === elements.length - 1" :placements="[PLACEMENTS.BEFORE, PLACEMENTS.AFTER]" :placements-disabled="getPlacementsDisabled(index)" :is-copying="copyingElementIndex === index" @move="moveElement(element, index, $event)" /> </div> </div> </div> </template> <script> import { mapGetters, mapActions } from 'vuex' import ElementPreview from '@baserow/modules/builder/components/elements/ElementPreview' import { notifyIf } from '@baserow/modules/core/utils/error' import PreviewNavigationBar from '@baserow/modules/builder/components/page/PreviewNavigationBar' import { PLACEMENTS } from '@baserow/modules/builder/enums' export default { name: 'PagePreview', components: { ElementPreview, PreviewNavigationBar }, data() { return { // The element that is currently being copied copyingElementIndex: null, // The resize observer to resize the preview when the wrapper size change resizeObserver: null, } }, computed: { PLACEMENTS: () => PLACEMENTS, ...mapGetters({ page: 'page/getSelected', deviceTypeSelected: 'page/getDeviceTypeSelected', elements: 'element/getRootElements', elementSelected: 'element/getSelected', }), deviceType() { return this.deviceTypeSelected ? this.$registry.get('device', this.deviceTypeSelected) : null }, maxWidth() { return this.deviceType?.maxWidth ? `${this.deviceType.maxWidth}px` : 'unset' }, }, watch: { deviceType(value) { this.$nextTick(() => { this.updatePreviewScale(value) }) }, }, mounted() { this.resizeObserver = new ResizeObserver(() => { this.onWindowResized() }) this.resizeObserver.observe(this.$el) this.onWindowResized() }, destroyed() { this.resizeObserver.unobserve(this.$el) }, methods: { ...mapActions({ actionMoveElement: 'element/move', actionSelectElement: 'element/select', actionUpdateElement: 'element/update', }), onWindowResized() { this.$nextTick(() => { this.updatePreviewScale(this.deviceType) }) }, updatePreviewScale(deviceType) { // The widths are the minimum width the preview must have. If the preview dom // element becomes smaller than the target, it will be scaled down so that the // actual width remains the same, and it will preview the correct device. const { clientWidth: currentWidth, clientHeight: currentHeight } = this.$refs.preview const targetWidth = deviceType?.minWidth let scale = 1 if (currentWidth < targetWidth) { // Round scale at 2 decimals scale = Math.round((currentWidth / targetWidth) * 100) / 100 } const previewScaled = this.$refs.previewScaled previewScaled.style.transform = `scale(${scale})` previewScaled.style.transformOrigin = `0 0` previewScaled.style.width = `${currentWidth / scale}px` previewScaled.style.height = `${currentHeight / scale}px` }, async moveElement(element, index, placement) { const elementToMoveId = element.id // BeforeElementId remains null if we are moving the element at the end of the // list let beforeElementId = null if (placement === PLACEMENTS.BEFORE) { beforeElementId = this.elements[index - 1].id } else if (index + 2 < this.elements.length) { beforeElementId = this.elements[index + 2].id } try { await this.actionMoveElement({ pageId: this.page.id, elementId: elementToMoveId, beforeElementId, }) } catch (error) { notifyIf(error) } }, getPlacementsDisabled(index) { const placementsDisabled = [] if (index === 0) { placementsDisabled.push(PLACEMENTS.BEFORE) } if (index === this.elements.length - 1) { placementsDisabled.push(PLACEMENTS.AFTER) } return placementsDisabled }, }, } </script>