Files
docuseal/app/javascript/template_builder/page.vue
T

724 lines
20 KiB
Vue
Raw Normal View History

2023-05-21 17:59:36 +03:00
<template>
2023-10-06 03:21:14 +03:00
<div
2025-07-14 12:53:15 +03:00
class="relative select-none mb-4 before:border before:rounded before:top-0 before:bottom-0 before:left-0 before:right-0 before:absolute"
2026-01-17 19:04:43 +02:00
:class="{ 'cursor-crosshair': allowDraw && editable && !isSelectMode, 'touch-none': !!drawField }"
2025-07-14 12:53:15 +03:00
style="container-type: size"
:style="{ aspectRatio: `${width} / ${height}`}"
2023-10-06 03:21:14 +03:00
>
2023-05-21 17:59:36 +03:00
<img
ref="image"
2023-10-22 01:54:20 +03:00
loading="lazy"
2023-05-21 17:59:36 +03:00
:src="image.url"
:width="width"
:height="height"
2025-07-14 12:53:15 +03:00
class="rounded"
2023-10-22 01:54:20 +03:00
@load="onImageLoad"
2023-05-21 17:59:36 +03:00
>
<div
class="top-0 bottom-0 left-0 right-0 absolute"
2023-06-03 00:53:46 +03:00
@pointerdown="onStartDraw"
2026-01-16 12:07:59 +02:00
@contextmenu="openContextMenu"
2023-05-21 17:59:36 +03:00
>
2026-01-17 19:04:43 +02:00
<SelectionBox
v-if="showSelectionBox"
:selection-box="selectionBox"
:page-width="width"
:page-height="height"
:is-resizing="!!resizeDirection"
:is-drawing="!!drawFieldType"
:is-drag="isDrag"
@move="onSelectionBoxMove"
@contextmenu="openSelectionContextMenu"
@close-context-menu="closeSelectionContextMenu"
/>
2023-05-21 17:59:36 +03:00
<FieldArea
v-for="(item, i) in areas"
:key="i"
2023-06-03 00:53:46 +03:00
:ref="setAreaRefs"
2023-06-04 01:08:45 +03:00
:area="item.area"
2024-07-16 19:01:44 +03:00
:input-mode="inputMode"
2025-09-26 20:52:44 +03:00
:page-width="width"
:page-height="height"
2023-05-21 17:59:36 +03:00
:field="item.field"
2023-11-04 01:19:32 +02:00
:editable="editable"
2024-07-15 14:03:36 +03:00
:with-field-placeholder="withFieldPlaceholder"
2025-07-17 10:54:44 +03:00
:with-signature-id="withSignatureId"
2025-07-27 10:27:12 +03:00
:with-prefillable="withPrefillable"
2025-01-07 17:30:21 +02:00
:default-field="defaultFieldsIndex[item.field.name]"
2024-01-23 23:20:19 +02:00
:default-submitters="defaultSubmitters"
2024-12-28 13:08:00 +02:00
:max-page="totalPages - 1"
2026-01-17 19:04:43 +02:00
:is-select-mode="isSelectMode"
2023-06-27 00:57:29 +03:00
@start-resize="resizeDirection = $event"
@stop-resize="resizeDirection = null"
2023-06-09 01:43:18 +03:00
@remove="$emit('remove-area', item.area)"
2024-05-05 16:57:04 +03:00
@scroll-to="$emit('scroll-to', $event)"
2026-01-26 11:11:19 +02:00
@add-custom-field="$emit('add-custom-field', $event)"
2026-01-16 12:07:59 +02:00
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
2023-05-21 17:59:36 +03:00
/>
<FieldArea
2026-01-26 11:11:19 +02:00
v-for="(area, index) in newAreas"
:key="index"
2023-06-27 00:57:29 +03:00
:is-draw="true"
2025-09-26 20:52:44 +03:00
:page-width="width"
:page-height="height"
2026-01-26 11:11:19 +02:00
:field="{ submitter_uuid: selectedSubmitter.uuid, type: newAreaFieldType }"
:area="area"
/>
<div
v-if="newAreas.length > 1"
class="absolute outline-dashed outline-gray-400 pointer-events-none z-20"
:style="newAreasBoxStyle"
2023-05-21 17:59:36 +03:00
/>
2026-01-17 19:04:43 +02:00
<div
v-if="selectionRect"
class="absolute outline-dashed outline-gray-400 pointer-events-none z-20"
:style="selectionRectStyle"
/>
2026-01-16 12:07:59 +02:00
<ContextMenu
v-if="contextMenu"
:context-menu="contextMenu"
:field="contextMenu.field"
:editable="editable"
2026-01-18 17:22:55 +02:00
:with-fields-detection="withFieldsDetection"
2026-01-16 12:07:59 +02:00
@copy="handleCopy"
@delete="handleDelete"
@paste="handlePaste"
2026-01-18 17:22:55 +02:00
@autodetect-fields="handleAutodetectFields"
2026-01-16 12:07:59 +02:00
@close="closeContextMenu"
/>
2026-01-17 19:04:43 +02:00
<ContextMenu
v-if="selectionContextMenu"
:context-menu="selectionContextMenu"
:editable="editable"
:is-multi-selection="true"
:selected-areas="selectedAreasRef.value"
:template="template"
@copy="handleSelectionCopy"
@delete="handleSelectionDelete"
@align="handleSelectionAlign"
@close="closeSelectionContextMenu"
/>
2023-05-21 17:59:36 +03:00
</div>
<div
2026-01-26 11:11:19 +02:00
v-show="resizeDirection || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value || customDragFieldRef?.value || selectionRect"
2023-05-21 17:59:36 +03:00
id="mask"
ref="mask"
2023-11-06 22:08:49 +02:00
class="top-0 bottom-0 left-0 right-0 absolute"
2026-01-17 19:04:43 +02:00
:class="{ 'z-10': !isMobile, 'cursor-grab': isDrag, 'cursor-nwse-resize': drawField && !isSelectMode, [resizeDirectionClasses[resizeDirection]]: !!resizeDirectionClasses }"
2023-05-21 17:59:36 +03:00
@pointermove="onPointermove"
2023-10-06 03:21:14 +03:00
@pointerdown="onStartDraw"
2026-01-16 12:07:59 +02:00
@contextmenu="openContextMenu"
2025-02-17 19:47:46 +02:00
@dragover.prevent="onDragover"
@dragenter="onDragenter"
2026-01-26 11:11:19 +02:00
@dragleave="newAreas = []"
2023-05-23 00:03:26 +03:00
@drop="onDrop"
2023-05-21 17:59:36 +03:00
@pointerup="onPointerup"
/>
</div>
</template>
<script>
import FieldArea from './area'
2026-01-16 12:07:59 +02:00
import ContextMenu from './context_menu'
2026-01-17 19:04:43 +02:00
import SelectionBox from './selection_box'
2023-05-21 17:59:36 +03:00
export default {
2023-05-31 23:19:20 +03:00
name: 'TemplatePage',
2023-05-21 17:59:36 +03:00
components: {
2026-01-16 12:07:59 +02:00
FieldArea,
2026-01-17 19:04:43 +02:00
ContextMenu,
SelectionBox
2023-05-21 17:59:36 +03:00
},
2026-01-26 11:11:19 +02:00
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'customDragFieldRef', 'assignDropAreaSize', 'selectedAreasRef', 'template', 'isSelectModeRef'],
2023-05-21 17:59:36 +03:00
props: {
image: {
type: Object,
required: true
},
2025-02-17 19:47:46 +02:00
dragFieldPlaceholder: {
type: Object,
required: false,
default: null
},
2025-07-17 10:54:44 +03:00
withSignatureId: {
type: Boolean,
required: false,
default: null
},
2025-07-27 10:27:12 +03:00
withPrefillable: {
type: Boolean,
required: false,
default: false
},
2023-05-21 17:59:36 +03:00
areas: {
type: Array,
required: false,
default: () => []
},
2024-07-16 19:01:44 +03:00
inputMode: {
type: Boolean,
required: false,
default: false
},
2023-11-27 20:24:58 +02:00
defaultFields: {
type: Array,
required: false,
default: () => []
},
2024-07-15 14:03:36 +03:00
withFieldPlaceholder: {
type: Boolean,
required: false,
default: false
},
2024-12-28 13:08:00 +02:00
totalPages: {
type: Number,
required: true
},
2024-03-27 13:47:17 +02:00
drawFieldType: {
type: String,
required: false,
default: ''
},
2024-01-05 18:40:45 +02:00
allowDraw: {
type: Boolean,
required: false,
default: true
},
2023-06-07 01:16:43 +03:00
selectedSubmitter: {
2023-06-04 01:08:45 +03:00
type: Object,
2023-06-07 01:16:43 +03:00
required: true
2023-06-04 01:08:45 +03:00
},
2024-01-23 23:20:19 +02:00
defaultSubmitters: {
type: Array,
required: false,
default: () => []
},
2023-06-27 00:57:29 +03:00
drawField: {
type: Object,
2023-06-08 00:20:42 +03:00
required: false,
2023-06-27 00:57:29 +03:00
default: null
2023-06-08 00:20:42 +03:00
},
2026-01-26 11:11:19 +02:00
drawCustomField: {
type: Object,
required: false,
default: null
},
2023-11-04 01:19:32 +02:00
editable: {
type: Boolean,
required: false,
default: true
},
2023-05-23 00:03:26 +03:00
isDrag: {
type: Boolean,
required: false,
default: false
},
2023-05-21 17:59:36 +03:00
number: {
type: Number,
required: true
2026-01-17 19:04:43 +02:00
},
attachmentUuid: {
type: String,
required: false,
default: ''
2026-01-18 17:22:55 +02:00
},
withFieldsDetection: {
type: Boolean,
required: false,
default: false
2023-05-21 17:59:36 +03:00
}
},
2026-01-26 11:11:19 +02:00
emits: ['draw', 'drop-field', 'remove-area', 'copy-field', 'paste-field', 'scroll-to', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas', 'autodetect-fields', 'add-custom-field'],
2023-05-21 17:59:36 +03:00
data () {
return {
2023-06-03 00:53:46 +03:00
areaRefs: [],
2023-05-21 17:59:36 +03:00
showMask: false,
2023-06-27 00:57:29 +03:00
resizeDirection: null,
2026-01-26 11:11:19 +02:00
newAreas: [],
2026-01-17 19:04:43 +02:00
contextMenu: null,
selectionRect: null,
selectionContextMenu: null
2023-05-21 17:59:36 +03:00
}
},
computed: {
2026-01-17 19:04:43 +02:00
isSelectMode () {
2026-01-19 11:57:09 +02:00
return this.isSelectModeRef.value && !this.drawFieldType && this.editable && !this.drawField
2026-01-17 19:04:43 +02:00
},
pageSelectedAreas () {
if (!this.selectedAreasRef.value) return []
return this.selectedAreasRef.value.filter((a) =>
a.attachment_uuid === this.attachmentUuid && a.page === this.number
)
},
showSelectionBox () {
return this.pageSelectedAreas.length >= 2 && this.editable
},
minSelectionBoxHeight () {
const ys = this.pageSelectedAreas.map((a) => a.y)
return Math.max(...ys) - Math.min(...ys)
},
minSelectionBoxWidth () {
const xs = this.pageSelectedAreas.map((a) => a.x)
return Math.max(...xs) - Math.min(...xs)
},
selectionBox () {
if (!this.pageSelectedAreas.length) return null
const minX = Math.min(...this.pageSelectedAreas.map((a) => a.x))
const minY = Math.min(...this.pageSelectedAreas.map((a) => a.y))
const maxX = Math.max(...this.pageSelectedAreas.map((a) => a.x + a.w))
const maxY = Math.max(...this.pageSelectedAreas.map((a) => a.y + a.h))
return {
x: minX,
y: minY,
w: Math.max(maxX - minX, this.minSelectionBoxWidth),
h: Math.max(maxY - minY, this.minSelectionBoxHeight)
}
},
2025-01-07 17:30:21 +02:00
defaultFieldsIndex () {
return this.defaultFields.reduce((acc, field) => {
acc[field.name] = field
return acc
}, {})
},
2026-01-26 11:11:19 +02:00
newAreaFieldType () {
if (this.drawField?.type) return this.drawField.type
if (this.drawCustomField?.type) return this.drawCustomField.type
if (this.dragFieldPlaceholder?.type) return this.dragFieldPlaceholder.type
if (this.customDragFieldRef?.value?.type) return this.customDragFieldRef.value.type
return this.defaultFieldType
},
2024-01-10 23:18:30 +02:00
defaultFieldType () {
2024-03-27 13:47:17 +02:00
if (this.drawFieldType) {
return this.drawFieldType
} else if (this.defaultDrawFieldType && this.defaultDrawFieldType !== 'text') {
2024-02-20 22:15:21 +02:00
return this.defaultDrawFieldType
} else if (this.fieldTypes.length !== 0 && !this.fieldTypes.includes('text')) {
2024-01-10 23:18:30 +02:00
return this.fieldTypes[0]
} else {
return 'text'
}
},
2023-10-06 03:21:14 +03:00
isMobile () {
2025-01-11 14:02:04 +02:00
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
2025-01-10 21:49:30 +02:00
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
2023-10-06 03:21:14 +03:00
},
2023-06-27 00:57:29 +03:00
resizeDirectionClasses () {
return {
nwse: 'cursor-nwse-resize',
ew: 'cursor-ew-resize'
}
},
2023-05-21 17:59:36 +03:00
width () {
return this.image.metadata.width
},
height () {
return this.image.metadata.height
2026-01-17 19:04:43 +02:00
},
2026-01-26 11:11:19 +02:00
newAreasBoxStyle () {
if (this.newAreas.length < 2) return {}
const minX = Math.min(...this.newAreas.map(a => a.x))
const minY = Math.min(...this.newAreas.map(a => a.y))
const maxX = Math.max(...this.newAreas.map(a => a.x + a.w))
const maxY = Math.max(...this.newAreas.map(a => a.y + a.h))
return {
left: minX * 100 + '%',
top: minY * 100 + '%',
width: (maxX - minX) * 100 + '%',
height: (maxY - minY) * 100 + '%'
}
},
2026-01-17 19:04:43 +02:00
selectionRectStyle () {
if (!this.selectionRect) return {}
return {
left: this.selectionRect.x * 100 + '%',
top: this.selectionRect.y * 100 + '%',
width: this.selectionRect.w * 100 + '%',
height: this.selectionRect.h * 100 + '%'
}
2023-05-21 17:59:36 +03:00
}
},
2023-06-03 00:53:46 +03:00
beforeUpdate () {
this.areaRefs = []
},
2023-05-21 17:59:36 +03:00
methods: {
2023-10-22 01:54:20 +03:00
onImageLoad (e) {
2025-07-14 12:53:15 +03:00
this.image.metadata.width = e.target.naturalWidth
this.image.metadata.height = e.target.naturalHeight
2023-10-22 01:54:20 +03:00
},
2026-01-16 12:07:59 +02:00
openContextMenu (event) {
if (!this.editable) {
return
}
event.preventDefault()
event.stopPropagation()
const rect = this.$refs.image.getBoundingClientRect()
2026-01-26 11:11:19 +02:00
this.newAreas = []
2026-01-16 12:07:59 +02:00
this.showMask = false
this.contextMenu = {
x: event.clientX,
y: event.clientY,
relativeX: (event.clientX - rect.left) / rect.width,
relativeY: (event.clientY - rect.top) / rect.height
}
},
openAreaContextMenu (event, area, field) {
if (!this.editable) {
return
}
event.preventDefault()
event.stopPropagation()
const rect = this.$refs.image.getBoundingClientRect()
2026-01-26 11:11:19 +02:00
this.newAreas = []
2026-01-16 12:07:59 +02:00
this.showMask = false
this.contextMenu = {
x: event.clientX,
y: event.clientY,
relativeX: (event.clientX - rect.left) / rect.width,
relativeY: (event.clientY - rect.top) / rect.height,
area,
field
}
},
2026-01-17 19:04:43 +02:00
openSelectionContextMenu (event) {
const rect = this.$el.getBoundingClientRect()
this.selectionContextMenu = {
x: event.clientX,
y: event.clientY,
relativeX: (event.clientX - rect.left) / rect.width,
relativeY: (event.clientY - rect.top) / rect.height
}
},
closeSelectionContextMenu () {
this.selectionContextMenu = null
},
handleSelectionCopy () {
this.$emit('copy-selected-areas')
this.closeSelectionContextMenu()
},
handleSelectionDelete () {
this.$emit('delete-selected-areas')
this.closeSelectionContextMenu()
},
handleSelectionAlign (direction) {
this.$emit('align-selected-areas', direction)
this.closeSelectionContextMenu()
},
2026-01-16 12:07:59 +02:00
closeContextMenu () {
this.contextMenu = null
2026-01-26 11:11:19 +02:00
this.newAreas = []
2026-01-16 12:07:59 +02:00
this.showMask = false
},
handleCopy () {
if (this.contextMenu.area) {
2026-01-17 19:04:43 +02:00
this.selectedAreasRef.value = [this.contextMenu.area]
2026-01-16 12:07:59 +02:00
this.$emit('copy-field')
}
this.closeContextMenu()
},
handleDelete () {
if (this.contextMenu.area) {
this.$emit('remove-area', this.contextMenu.area)
}
this.closeContextMenu()
},
handlePaste () {
2026-01-26 11:11:19 +02:00
this.newAreas = []
2026-01-16 12:07:59 +02:00
this.showMask = false
this.$emit('paste-field', {
page: this.number,
x: this.contextMenu.relativeX,
y: this.contextMenu.relativeY
})
this.closeContextMenu()
},
2026-01-18 17:22:55 +02:00
handleAutodetectFields () {
this.$emit('autodetect-fields', {
page: this.number,
attachmentUuid: this.attachmentUuid
})
this.closeContextMenu()
},
2023-06-03 00:53:46 +03:00
setAreaRefs (el) {
if (el) {
this.areaRefs.push(el)
}
},
2025-02-17 19:47:46 +02:00
onDragenter (e) {
2026-01-26 11:11:19 +02:00
const customField = this.customDragFieldRef?.value
const customAreas = customField?.areas || []
const dropX = (e.offsetX - 6) / this.$refs.mask.clientWidth
const dropY = e.offsetY / this.$refs.mask.clientHeight
if (customAreas.length > 1) {
const refArea = customAreas[0]
this.newAreas = customAreas.map((customArea) => ({
x: dropX + (customArea.x - refArea.x),
y: dropY + (customArea.y - refArea.y) - (customArea.h / 2),
w: customArea.w,
h: customArea.h
}))
} else {
const newArea = {}
if (customAreas.length === 1) {
newArea.w = customAreas[0].w
newArea.h = customAreas[0].h
} else if (customField) {
this.assignDropAreaSize(newArea, customField, {
maskW: this.$refs.mask.clientWidth,
maskH: this.$refs.mask.clientHeight
})
} else {
this.assignDropAreaSize(newArea, this.dragFieldPlaceholder, {
maskW: this.$refs.mask.clientWidth,
maskH: this.$refs.mask.clientHeight
})
}
2025-02-17 19:47:46 +02:00
2026-01-26 11:11:19 +02:00
newArea.x = dropX
newArea.y = dropY - newArea.h / 2
2025-02-17 19:47:46 +02:00
2026-01-26 11:11:19 +02:00
this.newAreas = [newArea]
}
2025-02-17 19:47:46 +02:00
},
onDragover (e) {
2026-01-26 11:11:19 +02:00
const customField = this.customDragFieldRef?.value
const customAreas = customField?.areas || []
const dropX = (e.offsetX - 6) / this.$refs.mask.clientWidth
const dropY = e.offsetY / this.$refs.mask.clientHeight
if (customAreas.length > 1) {
const refArea = customAreas[0]
this.newAreas.forEach((newArea, index) => {
const customArea = customAreas[index]
newArea.x = dropX + (customArea.x - refArea.x)
newArea.y = dropY + (customArea.y - refArea.y) - (customArea.h / 2)
})
} else if (this.newAreas.length) {
this.newAreas[0].x = dropX
this.newAreas[0].y = dropY - this.newAreas[0].h / 2
}
2025-02-17 19:47:46 +02:00
},
2023-05-23 00:03:26 +03:00
onDrop (e) {
2026-01-26 11:11:19 +02:00
this.newAreas = []
2025-02-17 19:47:46 +02:00
2023-05-23 00:03:26 +03:00
this.$emit('drop-field', {
2024-03-05 20:53:54 +02:00
x: e.offsetX,
y: e.offsetY,
2023-06-04 01:08:45 +03:00
maskW: this.$refs.mask.clientWidth,
maskH: this.$refs.mask.clientHeight,
2023-05-23 00:03:26 +03:00
page: this.number
})
},
2023-06-03 00:53:46 +03:00
onStartDraw (e) {
2026-01-16 12:07:59 +02:00
if (e.button === 2) {
return
}
2026-01-17 19:04:43 +02:00
if (this.selectedAreasRef.value.length >= 2) {
this.selectedAreasRef.value = []
}
if (this.isSelectMode) {
this.startSelectionRect(e)
return
}
2024-01-05 18:40:45 +02:00
if (!this.allowDraw) {
return
}
2026-01-26 11:11:19 +02:00
if (this.isMobile && !this.drawField && !this.drawCustomField) {
2023-10-06 03:21:14 +03:00
return
}
2023-11-04 01:19:32 +02:00
if (!this.editable) {
return
}
2023-06-03 00:53:46 +03:00
this.showMask = true
this.$nextTick(() => {
2026-01-26 11:11:19 +02:00
this.newAreas = [{
2024-03-05 20:53:54 +02:00
initialX: e.offsetX / this.$refs.mask.clientWidth,
initialY: e.offsetY / this.$refs.mask.clientHeight,
x: e.offsetX / this.$refs.mask.clientWidth,
y: e.offsetY / this.$refs.mask.clientHeight,
2023-05-21 17:59:36 +03:00
w: 0,
h: 0
2026-01-26 11:11:19 +02:00
}]
2023-06-03 00:53:46 +03:00
})
2023-05-21 17:59:36 +03:00
},
2026-01-17 19:04:43 +02:00
startSelectionRect (e) {
this.selectedAreasRef.value = []
this.showMask = true
this.$nextTick(() => {
const x = e.offsetX / this.$refs.mask.clientWidth
const y = e.offsetY / this.$refs.mask.clientHeight
this.selectionRect = {
initialX: x,
initialY: y,
x,
y,
w: 0,
h: 0
}
})
},
onSelectionBoxMove (dx, dy) {
let clampedDx = dx
let clampedDy = dy
this.pageSelectedAreas.forEach((area) => {
const maxDxLeft = -area.x
const maxDxRight = 1 - area.w - area.x
const maxDyTop = -area.y
const maxDyBottom = 1 - area.h - area.y
if (dx < maxDxLeft) clampedDx = Math.max(clampedDx, maxDxLeft)
if (dx > maxDxRight) clampedDx = Math.min(clampedDx, maxDxRight)
if (dy < maxDyTop) clampedDy = Math.max(clampedDy, maxDyTop)
if (dy > maxDyBottom) clampedDy = Math.min(clampedDy, maxDyBottom)
})
this.pageSelectedAreas.forEach((area) => {
area.x += clampedDx
area.y += clampedDy
})
},
2023-05-21 17:59:36 +03:00
onPointermove (e) {
2026-01-17 19:04:43 +02:00
if (this.selectionRect) {
const dx = e.offsetX / this.$refs.mask.clientWidth - this.selectionRect.initialX
const dy = e.offsetY / this.$refs.mask.clientHeight - this.selectionRect.initialY
if (dx > 0) {
this.selectionRect.x = this.selectionRect.initialX
} else {
this.selectionRect.x = e.offsetX / this.$refs.mask.clientWidth
}
if (dy > 0) {
this.selectionRect.y = this.selectionRect.initialY
} else {
this.selectionRect.y = e.offsetY / this.$refs.mask.clientHeight
}
this.selectionRect.w = Math.abs(dx)
this.selectionRect.h = Math.abs(dy)
return
}
2026-01-26 11:11:19 +02:00
const drawArea = this.newAreas[0]
if (drawArea?.initialX !== undefined) {
const dx = e.offsetX / this.$refs.mask.clientWidth - drawArea.initialX
const dy = e.offsetY / this.$refs.mask.clientHeight - drawArea.initialY
2023-05-21 17:59:36 +03:00
if (dx > 0) {
2026-01-26 11:11:19 +02:00
drawArea.x = drawArea.initialX
2023-05-21 17:59:36 +03:00
} else {
2026-01-26 11:11:19 +02:00
drawArea.x = e.offsetX / this.$refs.mask.clientWidth
2023-05-21 17:59:36 +03:00
}
if (dy > 0) {
2026-01-26 11:11:19 +02:00
drawArea.y = drawArea.initialY
2023-05-21 17:59:36 +03:00
} else {
2026-01-26 11:11:19 +02:00
drawArea.y = e.offsetY / this.$refs.mask.clientHeight
2023-05-21 17:59:36 +03:00
}
2026-01-26 11:11:19 +02:00
if ((this.drawField?.type || this.drawCustomField?.type || this.drawFieldType) === 'cells') {
drawArea.cell_w = drawArea.h * (this.$refs.mask.clientHeight / this.$refs.mask.clientWidth)
2023-06-27 00:57:29 +03:00
}
2026-01-26 11:11:19 +02:00
drawArea.w = Math.abs(dx)
drawArea.h = Math.abs(dy)
2023-05-21 17:59:36 +03:00
}
},
onPointerup (e) {
2026-01-17 19:04:43 +02:00
if (this.selectionRect) {
const selRect = this.selectionRect
const areasToSelect = this.areas || []
areasToSelect.forEach((item) => {
const area = item.area
if (this.rectsOverlap(selRect, area)) {
this.selectedAreasRef.value.push(area)
}
})
this.selectionRect = null
2026-01-26 11:11:19 +02:00
} else {
const drawArea = this.newAreas[0]
if (drawArea?.initialX !== undefined) {
const area = {
x: drawArea.x,
y: drawArea.y,
w: drawArea.w,
h: drawArea.h,
page: this.number
}
2023-06-27 00:57:29 +03:00
2026-01-26 11:11:19 +02:00
if ('cell_w' in drawArea) {
area.cell_w = drawArea.cell_w
}
2023-06-27 00:57:29 +03:00
2026-01-26 11:11:19 +02:00
const dx = Math.abs(e.offsetX - this.$refs.mask.clientWidth * drawArea.initialX)
const dy = Math.abs(e.offsetY - this.$refs.mask.clientHeight * drawArea.initialY)
2025-05-21 22:05:33 +03:00
2026-01-26 11:11:19 +02:00
const isTooSmall = dx < 8 && dy < 8
2025-05-21 22:05:33 +03:00
2026-01-26 11:11:19 +02:00
this.$emit('draw', { area, isTooSmall })
}
2023-05-21 17:59:36 +03:00
}
2023-06-03 00:53:46 +03:00
this.showMask = false
2026-01-26 11:11:19 +02:00
this.newAreas = []
2026-01-17 19:04:43 +02:00
},
rectsOverlap (r1, r2) {
return !(
r1.x + r1.w < r2.x ||
r2.x + r2.w < r1.x ||
r1.y + r1.h < r2.y ||
r2.y + r2.h < r1.y
)
2023-05-21 17:59:36 +03:00
}
}
}
</script>