add select fields mode
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
@touchstart="startTouchDrag"
|
@touchstart="startTouchDrag"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isSelected || isDraw"
|
v-if="isSelected || isDraw || isInMultiSelection"
|
||||||
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
|
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
|
||||||
:class="activeBorderClasses"
|
:class="activeBorderClasses"
|
||||||
/>
|
/>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
:style="{ left: (cellW / area.w * 100) + '%' }"
|
:style="{ left: (cellW / area.w * 100) + '%' }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="index === 0 && editable"
|
v-if="index === 0 && editable && !isInMultiSelection"
|
||||||
class="h-2.5 w-2.5 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-ew-resize z-10"
|
class="h-2.5 w-2.5 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-ew-resize z-10"
|
||||||
style="left: -4px"
|
style="left: -4px"
|
||||||
@mousedown.stop="startResizeCell"
|
@mousedown.stop="startResizeCell"
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field?.type && (isSelected || isNameFocus)"
|
v-if="field?.type && (isSelected || isNameFocus) && !isInMultiSelection"
|
||||||
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap flex z-10 field-area-controls"
|
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap flex z-10 field-area-controls"
|
||||||
style="top: -25px; height: 25px"
|
style="top: -25px; height: 25px"
|
||||||
@mousedown.stop
|
@mousedown.stop
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px] mt-[1px]'"
|
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px] mt-[1px]'"
|
||||||
:submitters="template.submitters"
|
:submitters="template.submitters"
|
||||||
@update:model-value="save"
|
@update:model-value="save"
|
||||||
@click="selectedAreaRef.value = area"
|
@click="selectedAreasRef.value = [area]"
|
||||||
/>
|
/>
|
||||||
<FieldType
|
<FieldType
|
||||||
v-model="field.type"
|
v-model="field.type"
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
:button-classes="'px-1'"
|
:button-classes="'px-1'"
|
||||||
:menu-classes="'bg-white rounded-t-none'"
|
:menu-classes="'bg-white rounded-t-none'"
|
||||||
@update:model-value="[maybeUpdateOptions(), save()]"
|
@update:model-value="[maybeUpdateOptions(), save()]"
|
||||||
@click="selectedAreaRef.value = area"
|
@click="selectedAreasRef.value = [area]"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
v-if="field.type !== 'checkbox' || field.name"
|
v-if="field.type !== 'checkbox' || field.name"
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
@click-font="isShowFontModal = true"
|
@click-font="isShowFontModal = true"
|
||||||
@click-description="isShowDescriptionModal = true"
|
@click-description="isShowDescriptionModal = true"
|
||||||
@click-condition="isShowConditionsModal = true"
|
@click-condition="isShowConditionsModal = true"
|
||||||
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
@scroll-to="[selectedAreasRef.value = [$event], $emit('scroll-to', $event)]"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
ref="defaultValueSelect"
|
ref="defaultValueSelect"
|
||||||
class="bg-transparent outline-none focus:outline-none w-full"
|
class="bg-transparent outline-none focus:outline-none w-full"
|
||||||
@change="[field.default_value = $event.target.value, field.readonly = !!field.default_value?.length, save()]"
|
@change="[field.default_value = $event.target.value, field.readonly = !!field.default_value?.length, save()]"
|
||||||
@focus="selectedAreaRef.value = area"
|
@focus="selectedAreasRef.value = [area]"
|
||||||
@keydown.enter="onDefaultValueEnter"
|
@keydown.enter="onDefaultValueEnter"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
@@ -293,7 +293,7 @@
|
|||||||
:class="{ 'cursor-text': isValueInput }"
|
:class="{ 'cursor-text': isValueInput }"
|
||||||
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : (field.type === 'date' ? field.preferences?.format || t('type_value') : t('type_value'))"
|
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : (field.type === 'date' ? field.preferences?.format || t('type_value') : t('type_value'))"
|
||||||
@blur="onDefaultValueBlur"
|
@blur="onDefaultValueBlur"
|
||||||
@focus="selectedAreaRef.value = area"
|
@focus="selectedAreasRef.value = [area]"
|
||||||
@paste.prevent="onPaste"
|
@paste.prevent="onPaste"
|
||||||
@keydown.enter="onDefaultValueEnter"
|
@keydown.enter="onDefaultValueEnter"
|
||||||
>{{ field.default_value }}</span>
|
>{{ field.default_value }}</span>
|
||||||
@@ -319,6 +319,7 @@
|
|||||||
<span
|
<span
|
||||||
v-if="field?.type && editable"
|
v-if="field?.type && editable"
|
||||||
class="h-4 w-4 lg:h-2.5 lg:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
class="h-4 w-4 lg:h-2.5 lg:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
||||||
|
:class="{ 'z-30': isInMultiSelection }"
|
||||||
@mousedown.stop="startResize"
|
@mousedown.stop="startResize"
|
||||||
@touchstart="startTouchResize"
|
@touchstart="startTouchResize"
|
||||||
/>
|
/>
|
||||||
@@ -398,7 +399,7 @@ export default {
|
|||||||
FieldSubmitter,
|
FieldSubmitter,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 'selectedAreaRef', 'save', 't', 'isInlineSize'],
|
inject: ['template', 'save', 't', 'isInlineSize', 'selectedAreasRef', 'isCmdKeyRef'],
|
||||||
props: {
|
props: {
|
||||||
area: {
|
area: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -463,6 +464,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
isSelectMode: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to'],
|
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to'],
|
||||||
@@ -646,7 +652,10 @@ export default {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
isSelected () {
|
isSelected () {
|
||||||
return this.selectedAreaRef.value === this.area
|
return this.selectedAreasRef.value.includes(this.area)
|
||||||
|
},
|
||||||
|
isInMultiSelection () {
|
||||||
|
return this.selectedAreasRef.value.length >= 2 && this.isSelected
|
||||||
},
|
},
|
||||||
positionStyle () {
|
positionStyle () {
|
||||||
const { x, y, w, h } = this.area
|
const { x, y, w, h } = this.area
|
||||||
@@ -683,10 +692,10 @@ export default {
|
|||||||
buildAreaOptionValue (area) {
|
buildAreaOptionValue (area) {
|
||||||
const option = this.optionsUuidIndex[area.option_uuid]
|
const option = this.optionsUuidIndex[area.option_uuid]
|
||||||
|
|
||||||
return option.value || `${this.t('option')} ${this.field.options.indexOf(option) + 1}`
|
return option?.value || `${this.t('option')} ${this.field.options.indexOf(option) + 1}`
|
||||||
},
|
},
|
||||||
maybeToggleDefaultValue () {
|
maybeToggleDefaultValue () {
|
||||||
if (!this.editable) {
|
if (!this.editable || this.isCmdKeyRef.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,7 +779,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onNameFocus (e) {
|
onNameFocus (e) {
|
||||||
this.selectedAreaRef.value = this.area
|
this.selectedAreasRef.value = [this.area]
|
||||||
|
|
||||||
this.isNameFocus = true
|
this.isNameFocus = true
|
||||||
this.$refs.name.style.minWidth = this.$refs.name.clientWidth + 'px'
|
this.$refs.name.style.minWidth = this.$refs.name.clientWidth + 'px'
|
||||||
@@ -906,6 +915,15 @@ export default {
|
|||||||
if (e.target.id === 'mask') {
|
if (e.target.id === 'mask') {
|
||||||
this.area.w = e.offsetX / e.target.clientWidth - this.area.x
|
this.area.w = e.offsetX / e.target.clientWidth - this.area.x
|
||||||
this.area.h = e.offsetY / e.target.clientHeight - this.area.y
|
this.area.h = e.offsetY / e.target.clientHeight - this.area.y
|
||||||
|
|
||||||
|
if (this.isInMultiSelection) {
|
||||||
|
this.selectedAreasRef.value.forEach((area) => {
|
||||||
|
if (area !== this.area) {
|
||||||
|
area.w = this.area.w
|
||||||
|
area.h = this.area.h
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
drag (e) {
|
drag (e) {
|
||||||
@@ -931,7 +949,7 @@ export default {
|
|||||||
|
|
||||||
const rect = e.target.getBoundingClientRect()
|
const rect = e.target.getBoundingClientRect()
|
||||||
|
|
||||||
this.selectedAreaRef.value = this.area
|
this.selectedAreasRef.value = [this.area]
|
||||||
|
|
||||||
this.dragFrom = { x: rect.left - e.touches[0].clientX, y: rect.top - e.touches[0].clientY }
|
this.dragFrom = { x: rect.left - e.touches[0].clientX, y: rect.top - e.touches[0].clientY }
|
||||||
|
|
||||||
@@ -980,13 +998,23 @@ export default {
|
|||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (e.metaKey || e.ctrlKey) {
|
||||||
|
if (!this.selectedAreasRef.value.includes(this.area)) {
|
||||||
|
this.selectedAreasRef.value.push(this.area)
|
||||||
|
} else {
|
||||||
|
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (this.editable) {
|
if (this.editable) {
|
||||||
this.isDragged = true
|
this.isDragged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const rect = e.target.getBoundingClientRect()
|
const rect = e.target.getBoundingClientRect()
|
||||||
|
|
||||||
this.selectedAreaRef.value = this.area
|
this.selectedAreasRef.value = [this.area]
|
||||||
|
|
||||||
this.dragFrom = { x: rect.left - e.clientX, y: rect.top - e.clientY }
|
this.dragFrom = { x: rect.left - e.clientX, y: rect.top - e.clientY }
|
||||||
|
|
||||||
@@ -1055,7 +1083,9 @@ export default {
|
|||||||
this.$emit('stop-drag')
|
this.$emit('stop-drag')
|
||||||
},
|
},
|
||||||
startResize () {
|
startResize () {
|
||||||
this.selectedAreaRef.value = this.area
|
if (!this.selectedAreasRef.value.includes(this.area)) {
|
||||||
|
this.selectedAreasRef.value = [this.area]
|
||||||
|
}
|
||||||
|
|
||||||
this.$el.getRootNode().addEventListener('mousemove', this.resize)
|
this.$el.getRootNode().addEventListener('mousemove', this.resize)
|
||||||
this.$el.getRootNode().addEventListener('mouseup', this.stopResize)
|
this.$el.getRootNode().addEventListener('mouseup', this.stopResize)
|
||||||
@@ -1071,7 +1101,9 @@ export default {
|
|||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
startTouchResize (e) {
|
startTouchResize (e) {
|
||||||
this.selectedAreaRef.value = this.area
|
if (!this.selectedAreasRef.value.includes(this.area)) {
|
||||||
|
this.selectedAreasRef.value = [this.area]
|
||||||
|
}
|
||||||
|
|
||||||
this.$refs?.name?.blur()
|
this.$refs?.name?.blur()
|
||||||
|
|
||||||
@@ -1088,6 +1120,15 @@ export default {
|
|||||||
|
|
||||||
this.area.w = (e.touches[0].clientX - rect.left) / rect.width - this.area.x
|
this.area.w = (e.touches[0].clientX - rect.left) / rect.width - this.area.x
|
||||||
this.area.h = (e.touches[0].clientY - rect.top) / rect.height - this.area.y
|
this.area.h = (e.touches[0].clientY - rect.top) / rect.height - this.area.y
|
||||||
|
|
||||||
|
if (this.isInMultiSelection) {
|
||||||
|
this.selectedAreasRef.value.forEach((area) => {
|
||||||
|
if (area !== this.area) {
|
||||||
|
area.w = this.area.w
|
||||||
|
area.h = this.area.h
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
stopTouchResize () {
|
stopTouchResize () {
|
||||||
this.$el.getRootNode().removeEventListener('touchmove', this.touchResize)
|
this.$el.getRootNode().removeEventListener('touchmove', this.touchResize)
|
||||||
|
|||||||
@@ -378,6 +378,9 @@
|
|||||||
@remove-area="removeArea"
|
@remove-area="removeArea"
|
||||||
@paste-field="pasteField"
|
@paste-field="pasteField"
|
||||||
@copy-field="copyField"
|
@copy-field="copyField"
|
||||||
|
@copy-selected-areas="copySelectedAreas"
|
||||||
|
@delete-selected-areas="deleteSelectedAreas"
|
||||||
|
@align-selected-areas="alignSelectedAreas"
|
||||||
/>
|
/>
|
||||||
<DocumentControls
|
<DocumentControls
|
||||||
v-if="isBreakpointLg && editable"
|
v-if="isBreakpointLg && editable"
|
||||||
@@ -592,8 +595,10 @@ export default {
|
|||||||
withConditions: this.withConditions,
|
withConditions: this.withConditions,
|
||||||
isInlineSize: this.isInlineSize,
|
isInlineSize: this.isInlineSize,
|
||||||
defaultDrawFieldType: this.defaultDrawFieldType,
|
defaultDrawFieldType: this.defaultDrawFieldType,
|
||||||
selectedAreaRef: computed(() => this.selectedAreaRef),
|
selectedAreasRef: computed(() => this.selectedAreasRef),
|
||||||
fieldsDragFieldRef: computed(() => this.fieldsDragFieldRef)
|
fieldsDragFieldRef: computed(() => this.fieldsDragFieldRef),
|
||||||
|
isSelectModeRef: computed(() => this.isSelectModeRef),
|
||||||
|
isCmdKeyRef: computed(() => this.isCmdKeyRef)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -862,8 +867,10 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
submitterDefaultNames: FieldSubmitter.computed.names,
|
submitterDefaultNames: FieldSubmitter.computed.names,
|
||||||
selectedAreaRef: () => ref(),
|
isSelectModeRef: () => ref(false),
|
||||||
|
isCmdKeyRef: () => ref(false),
|
||||||
fieldsDragFieldRef: () => ref(),
|
fieldsDragFieldRef: () => ref(),
|
||||||
|
selectedAreasRef: () => ref([]),
|
||||||
language () {
|
language () {
|
||||||
return this.locale.split('-')[0].toLowerCase()
|
return this.locale.split('-')[0].toLowerCase()
|
||||||
},
|
},
|
||||||
@@ -877,6 +884,14 @@ export default {
|
|||||||
isInlineSize () {
|
isInlineSize () {
|
||||||
return CSS.supports('container-type: size')
|
return CSS.supports('container-type: size')
|
||||||
},
|
},
|
||||||
|
lowestSelectedArea () {
|
||||||
|
return this.selectedAreasRef.value.reduce((acc, area) => {
|
||||||
|
return area.y + area.h < acc.y + acc.h ? acc : area
|
||||||
|
}, this.selectedAreasRef.value[0])
|
||||||
|
},
|
||||||
|
lastSelectedArea () {
|
||||||
|
return this.selectedAreasRef.value[this.selectedAreasRef.value.length - 1]
|
||||||
|
},
|
||||||
isMobile () {
|
isMobile () {
|
||||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||||
|
|
||||||
@@ -918,7 +933,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
selectedField () {
|
selectedField () {
|
||||||
return this.template.fields.find((f) => f.areas?.includes(this.selectedAreaRef.value))
|
return this.template.fields.find((f) => f.areas?.includes(this.lastSelectedArea))
|
||||||
},
|
},
|
||||||
sortedDocuments () {
|
sortedDocuments () {
|
||||||
return this.template.schema.map((item) => {
|
return this.template.schema.map((item) => {
|
||||||
@@ -1005,6 +1020,81 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toRaw,
|
toRaw,
|
||||||
|
toggleSelectMode () {
|
||||||
|
this.isSelectModeRef.value = !this.isSelectModeRef.value
|
||||||
|
|
||||||
|
if (!this.isSelectModeRef.value && this.selectedAreasRef.value.length > 1) {
|
||||||
|
this.selectedAreasRef.value = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteSelectedAreas () {
|
||||||
|
[...this.selectedAreasRef.value].forEach((area) => {
|
||||||
|
this.removeArea(area, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
|
moveSelectedAreas (dx, dy) {
|
||||||
|
let clampedDx = dx
|
||||||
|
let clampedDy = dy
|
||||||
|
|
||||||
|
const rectIndex = {}
|
||||||
|
|
||||||
|
this.selectedAreasRef.value.map((area) => {
|
||||||
|
const key = `${area.attachment_uuid}-${area.page}`
|
||||||
|
|
||||||
|
let rect = rectIndex[key]
|
||||||
|
|
||||||
|
if (!rect) {
|
||||||
|
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||||
|
const page = documentRef.pageRefs[area.page].$refs.image
|
||||||
|
rect = page.getBoundingClientRect()
|
||||||
|
|
||||||
|
rectIndex[key] = rect
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedDx = dx / rect.width
|
||||||
|
const normalizedDy = dy / rect.height
|
||||||
|
|
||||||
|
const maxDxLeft = -area.x
|
||||||
|
const maxDxRight = 1 - area.w - area.x
|
||||||
|
const maxDyTop = -area.y
|
||||||
|
const maxDyBottom = 1 - area.h - area.y
|
||||||
|
|
||||||
|
if (normalizedDx < maxDxLeft) clampedDx = Math.max(clampedDx, maxDxLeft * rect.width)
|
||||||
|
if (normalizedDx > maxDxRight) clampedDx = Math.min(clampedDx, maxDxRight * rect.width)
|
||||||
|
if (normalizedDy < maxDyTop) clampedDy = Math.max(clampedDy, maxDyTop * rect.height)
|
||||||
|
if (normalizedDy > maxDyBottom) clampedDy = Math.min(clampedDy, maxDyBottom * rect.height)
|
||||||
|
|
||||||
|
return [area, rect]
|
||||||
|
}).forEach(([area, rect]) => {
|
||||||
|
area.x += clampedDx / rect.width
|
||||||
|
area.y += clampedDy / rect.height
|
||||||
|
})
|
||||||
|
|
||||||
|
this.debouncedSave()
|
||||||
|
},
|
||||||
|
alignSelectedAreas (direction) {
|
||||||
|
const areas = this.selectedAreasRef.value
|
||||||
|
|
||||||
|
let targetValue
|
||||||
|
|
||||||
|
if (direction === 'left') {
|
||||||
|
targetValue = Math.min(...areas.map(a => a.x))
|
||||||
|
areas.forEach((area) => { area.x = targetValue })
|
||||||
|
} else if (direction === 'right') {
|
||||||
|
targetValue = Math.max(...areas.map(a => a.x + a.w))
|
||||||
|
areas.forEach((area) => { area.x = targetValue - area.w })
|
||||||
|
} else if (direction === 'top') {
|
||||||
|
targetValue = Math.min(...areas.map(a => a.y))
|
||||||
|
areas.forEach((area) => { area.y = targetValue })
|
||||||
|
} else if (direction === 'bottom') {
|
||||||
|
targetValue = Math.max(...areas.map(a => a.y + a.h))
|
||||||
|
areas.forEach((area) => { area.y = targetValue - area.h })
|
||||||
|
}
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
download () {
|
download () {
|
||||||
this.isDownloading = true
|
this.isDownloading = true
|
||||||
|
|
||||||
@@ -1389,49 +1479,92 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onKeyUp (e) {
|
onKeyUp (e) {
|
||||||
if (e.code === 'Escape') {
|
this.isCmdKeyRef.value = false
|
||||||
this.clearDrawField()
|
|
||||||
|
|
||||||
this.selectedAreaRef.value = null
|
if (e.code === 'Escape') {
|
||||||
|
this.selectedAreasRef.value = []
|
||||||
|
this.clearDrawField()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.editable && ['Backspace', 'Delete'].includes(e.key) && this.selectedAreaRef.value && document.activeElement === document.body) {
|
if (this.editable && ['Backspace', 'Delete'].includes(e.key) && document.activeElement === document.body) {
|
||||||
this.removeArea(this.selectedAreaRef.value)
|
if (this.selectedAreasRef.value.length > 1) {
|
||||||
|
this.deleteSelectedAreas()
|
||||||
this.selectedAreaRef.value = null
|
} else if (this.selectedAreasRef.value.length) {
|
||||||
|
this.removeArea(this.lastSelectedArea)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onKeyDown (event) {
|
onKeyDown (event) {
|
||||||
if ((event.metaKey && event.shiftKey && event.key === 'z') || (event.ctrlKey && event.key === 'Z')) {
|
if (event.key === 'Tab' && document.activeElement === document.body) {
|
||||||
event.stopImmediatePropagation()
|
event.stopImmediatePropagation()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
this.toggleSelectMode()
|
||||||
|
} else if ((event.metaKey && event.shiftKey && event.key === 'z') || (event.ctrlKey && event.key === 'Z')) {
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
this.selectedAreasRef.value = []
|
||||||
|
|
||||||
this.redo()
|
this.redo()
|
||||||
} else if ((event.ctrlKey || event.metaKey) && event.key === 'z') {
|
} else if ((event.ctrlKey || event.metaKey) && event.key === 'z') {
|
||||||
event.stopImmediatePropagation()
|
event.stopImmediatePropagation()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
this.selectedAreasRef.value = []
|
||||||
|
|
||||||
this.undo()
|
this.undo()
|
||||||
} else if ((event.ctrlKey || event.metaKey) && event.key === 'c' && document.activeElement === document.body) {
|
} else if ((event.ctrlKey || event.metaKey) && event.key === 'c' && document.activeElement === document.body) {
|
||||||
event.preventDefault()
|
if (this.selectedAreasRef.value.length > 1) {
|
||||||
|
event.preventDefault()
|
||||||
this.copyField()
|
this.copySelectedAreas()
|
||||||
|
} else if (this.selectedAreasRef.value.length) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.copyField()
|
||||||
|
}
|
||||||
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v' && this.hasClipboardData() && document.activeElement === document.body) {
|
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v' && this.hasClipboardData() && document.activeElement === document.body) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
this.pasteField()
|
this.pasteField()
|
||||||
} else if (this.selectedAreaRef.value && ['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown'].includes(event.key) && document.activeElement === document.body) {
|
} else if (['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown'].includes(event.key) && document.activeElement === document.body) {
|
||||||
event.preventDefault()
|
if (this.selectedAreasRef.value.length > 1) {
|
||||||
|
event.preventDefault()
|
||||||
this.handleAreaArrows(event)
|
this.handleSelectedAreasArrows(event)
|
||||||
|
} else if (this.selectedAreasRef.value.length) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.handleAreaArrows(event)
|
||||||
|
}
|
||||||
|
} else if (event.metaKey || event.ctrlKey) {
|
||||||
|
this.isCmdKeyRef.value = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleSelectedAreasArrows (event) {
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = (event.shiftKey ? 5.0 : 1.0)
|
||||||
|
let dx = 0
|
||||||
|
let dy = 0
|
||||||
|
|
||||||
|
if (event.key === 'ArrowRight') {
|
||||||
|
dx = diff
|
||||||
|
} else if (event.key === 'ArrowLeft') {
|
||||||
|
dx = -diff
|
||||||
|
} else if (event.key === 'ArrowUp') {
|
||||||
|
dy = -diff
|
||||||
|
} else if (event.key === 'ArrowDown') {
|
||||||
|
dy = diff
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moveSelectedAreas(dx, dy)
|
||||||
|
},
|
||||||
handleAreaArrows (event) {
|
handleAreaArrows (event) {
|
||||||
if (!this.editable) {
|
if (!this.editable) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const area = this.selectedAreaRef.value
|
const area = this.lastSelectedArea
|
||||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||||
const page = documentRef.pageRefs[area.page].$refs.image
|
const page = documentRef.pageRefs[area.page].$refs.image
|
||||||
const rect = page.getBoundingClientRect()
|
const rect = page.getBoundingClientRect()
|
||||||
@@ -1464,7 +1597,7 @@ export default {
|
|||||||
this.save()
|
this.save()
|
||||||
}, 700)
|
}, 700)
|
||||||
},
|
},
|
||||||
removeArea (area) {
|
removeArea (area, save = true) {
|
||||||
const field = this.template.fields.find((f) => f.areas?.includes(area))
|
const field = this.template.fields.find((f) => f.areas?.includes(area))
|
||||||
|
|
||||||
field.areas.splice(field.areas.indexOf(area), 1)
|
field.areas.splice(field.areas.indexOf(area), 1)
|
||||||
@@ -1475,7 +1608,11 @@ export default {
|
|||||||
this.removeFieldConditions(field)
|
this.removeFieldConditions(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save()
|
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(area), 1)
|
||||||
|
|
||||||
|
if (save) {
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
removeFieldConditions (field) {
|
removeFieldConditions (field) {
|
||||||
this.template.fields.forEach((f) => {
|
this.template.fields.forEach((f) => {
|
||||||
@@ -1499,7 +1636,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
copyField () {
|
copyField () {
|
||||||
const area = this.selectedAreaRef.value
|
const area = this.lastSelectedArea
|
||||||
|
|
||||||
if (!area) return
|
if (!area) return
|
||||||
|
|
||||||
@@ -1524,25 +1661,68 @@ export default {
|
|||||||
console.error('Failed to save clipboard:', e)
|
console.error('Failed to save clipboard:', e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pasteField (targetPosition = null) {
|
copySelectedAreas () {
|
||||||
let field = null
|
const items = []
|
||||||
let area = null
|
|
||||||
let isSameTemplate = false
|
|
||||||
|
|
||||||
|
const areas = this.selectedAreasRef.value
|
||||||
|
|
||||||
|
const minX = Math.min(...areas.map(a => a.x))
|
||||||
|
const minY = Math.min(...areas.map(a => a.y))
|
||||||
|
|
||||||
|
areas.forEach((area) => {
|
||||||
|
const field = this.template.fields.find((f) => f.areas?.includes(area))
|
||||||
|
|
||||||
|
if (!field) return
|
||||||
|
|
||||||
|
const fieldCopy = JSON.parse(JSON.stringify(field))
|
||||||
|
const areaCopy = JSON.parse(JSON.stringify(area))
|
||||||
|
|
||||||
|
delete fieldCopy.areas
|
||||||
|
delete fieldCopy.uuid
|
||||||
|
delete fieldCopy.submitter_uuid
|
||||||
|
|
||||||
|
areaCopy.relativeX = area.x - minX
|
||||||
|
areaCopy.relativeY = area.y - minY
|
||||||
|
|
||||||
|
items.push({ field: fieldCopy, area: areaCopy })
|
||||||
|
})
|
||||||
|
|
||||||
|
const clipboardData = {
|
||||||
|
items,
|
||||||
|
templateId: this.template.id,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
isGroup: true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.setItem('docuseal_clipboard', JSON.stringify(clipboardData))
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save clipboard:', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pasteField (targetPosition = null) {
|
||||||
const clipboard = localStorage.getItem('docuseal_clipboard')
|
const clipboard = localStorage.getItem('docuseal_clipboard')
|
||||||
|
|
||||||
if (clipboard) {
|
if (!clipboard) return
|
||||||
const data = JSON.parse(clipboard)
|
|
||||||
|
|
||||||
if (Date.now() - data.timestamp < 3600000) {
|
const data = JSON.parse(clipboard)
|
||||||
field = data.field
|
|
||||||
area = data.area
|
if (Date.now() - data.timestamp >= 3600000) {
|
||||||
isSameTemplate = data.templateId === this.template.id
|
localStorage.removeItem('docuseal_clipboard')
|
||||||
} else {
|
|
||||||
localStorage.removeItem('docuseal_clipboard')
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.isGroup && data.items?.length) {
|
||||||
|
this.pasteFieldGroup(data, targetPosition)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const field = data.field
|
||||||
|
const area = data.area
|
||||||
|
const isSameTemplate = data.templateId === this.template.id
|
||||||
|
|
||||||
if (!field || !area) return
|
if (!field || !area) return
|
||||||
|
|
||||||
if (!isSameTemplate) {
|
if (!isSameTemplate) {
|
||||||
@@ -1550,20 +1730,19 @@ export default {
|
|||||||
delete field.preferences?.formula
|
delete field.preferences?.formula
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentArea = this.selectedAreaRef.value
|
|
||||||
const defaultAttachmentUuid = this.template.schema[0]?.attachment_uuid
|
const defaultAttachmentUuid = this.template.schema[0]?.attachment_uuid
|
||||||
|
|
||||||
if (field && (currentArea || targetPosition)) {
|
if (field && (this.lowestSelectedArea || targetPosition)) {
|
||||||
const attachmentUuid = targetPosition?.attachment_uuid ||
|
const attachmentUuid = targetPosition?.attachment_uuid ||
|
||||||
(this.template.documents.find((d) => d.uuid === currentArea.attachment_uuid) ? currentArea.attachment_uuid : null) ||
|
(this.template.documents.find((d) => d.uuid === this.lowestSelectedArea.attachment_uuid) ? this.lowestSelectedArea.attachment_uuid : null) ||
|
||||||
defaultAttachmentUuid
|
defaultAttachmentUuid
|
||||||
|
|
||||||
const newArea = {
|
const newArea = {
|
||||||
...JSON.parse(JSON.stringify(area)),
|
...JSON.parse(JSON.stringify(area)),
|
||||||
attachment_uuid: attachmentUuid,
|
attachment_uuid: attachmentUuid,
|
||||||
page: targetPosition?.page ?? (attachmentUuid === currentArea.attachment_uuid ? currentArea.page : 0),
|
page: targetPosition?.page ?? (attachmentUuid === this.lowestSelectedArea.attachment_uuid ? this.lowestSelectedArea.page : 0),
|
||||||
x: targetPosition ? (targetPosition.x - area.w / 2) : currentArea.x,
|
x: targetPosition ? (targetPosition.x - area.w / 2) : Math.min(...this.selectedAreasRef.value.map((area) => area.x)),
|
||||||
y: targetPosition ? (targetPosition.y - area.h / 2) : (currentArea.y + currentArea.h * 1.3)
|
y: targetPosition ? (targetPosition.y - area.h / 2) : (this.lowestSelectedArea.y + this.lowestSelectedArea.h * 1.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
const newField = {
|
const newField = {
|
||||||
@@ -1588,11 +1767,85 @@ export default {
|
|||||||
|
|
||||||
this.insertField(newField)
|
this.insertField(newField)
|
||||||
|
|
||||||
this.selectedAreaRef.value = newArea
|
this.selectedAreasRef.value = [newArea]
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pasteFieldGroup (data, targetPosition) {
|
||||||
|
const isSameTemplate = data.templateId === this.template.id
|
||||||
|
const defaultAttachmentUuid = this.template.schema[0]?.attachment_uuid
|
||||||
|
|
||||||
|
const attachmentUuid = targetPosition?.attachment_uuid ||
|
||||||
|
(this.lowestSelectedArea && this.template.documents.find((d) => d.uuid === this.lowestSelectedArea.attachment_uuid) ? this.lowestSelectedArea.attachment_uuid : null) ||
|
||||||
|
defaultAttachmentUuid
|
||||||
|
|
||||||
|
const page = targetPosition?.page ?? (this.lowestSelectedArea && attachmentUuid === this.lowestSelectedArea.attachment_uuid ? this.lowestSelectedArea.page : 0)
|
||||||
|
|
||||||
|
let baseX, baseY
|
||||||
|
|
||||||
|
if (targetPosition) {
|
||||||
|
baseX = targetPosition.x
|
||||||
|
baseY = targetPosition.y
|
||||||
|
} else if (this.lowestSelectedArea) {
|
||||||
|
baseX = Math.min(...this.selectedAreasRef.value.map((area) => area.x))
|
||||||
|
baseY = this.lowestSelectedArea.y + this.lowestSelectedArea.h * 1.3
|
||||||
|
} else {
|
||||||
|
baseX = 0.1
|
||||||
|
baseY = 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
const newAreas = []
|
||||||
|
|
||||||
|
data.items.forEach((item) => {
|
||||||
|
const field = JSON.parse(JSON.stringify(item.field))
|
||||||
|
const area = JSON.parse(JSON.stringify(item.area))
|
||||||
|
|
||||||
|
if (!isSameTemplate) {
|
||||||
|
delete field.conditions
|
||||||
|
delete field.preferences?.formula
|
||||||
|
}
|
||||||
|
|
||||||
|
const newArea = {
|
||||||
|
...area,
|
||||||
|
attachment_uuid: attachmentUuid,
|
||||||
|
page,
|
||||||
|
x: baseX + (area.relativeX || 0),
|
||||||
|
y: baseY + (area.relativeY || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete newArea.relativeX
|
||||||
|
delete newArea.relativeY
|
||||||
|
|
||||||
|
const newField = {
|
||||||
|
...field,
|
||||||
|
uuid: v4(),
|
||||||
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
|
areas: [newArea]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['radio', 'multiple'].includes(field.type) && field.options?.length) {
|
||||||
|
const oldOptionUuid = area.option_uuid
|
||||||
|
const optionsMap = {}
|
||||||
|
|
||||||
|
newField.options = field.options.map((opt) => {
|
||||||
|
const newUuid = v4()
|
||||||
|
optionsMap[opt.uuid] = newUuid
|
||||||
|
|
||||||
|
return { ...opt, uuid: newUuid }
|
||||||
|
})
|
||||||
|
|
||||||
|
newArea.option_uuid = optionsMap[oldOptionUuid] || newField.options[0].uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
this.insertField(newField)
|
||||||
|
newAreas.push(newArea)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.selectedAreasRef.value = [...newAreas]
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
hasClipboardData () {
|
hasClipboardData () {
|
||||||
try {
|
try {
|
||||||
const clipboard = localStorage.getItem('docuseal_clipboard')
|
const clipboard = localStorage.getItem('docuseal_clipboard')
|
||||||
@@ -1659,8 +1912,8 @@ export default {
|
|||||||
const previousArea = this.drawField.areas?.[this.drawField.areas.length - 1]
|
const previousArea = this.drawField.areas?.[this.drawField.areas.length - 1]
|
||||||
|
|
||||||
if (this.selectedField?.type === this.drawField.type) {
|
if (this.selectedField?.type === this.drawField.type) {
|
||||||
area.w = this.selectedAreaRef.value.w
|
area.w = this.lastSelectedArea.w
|
||||||
area.h = this.selectedAreaRef.value.h
|
area.h = this.lastSelectedArea.h
|
||||||
} else if (previousArea) {
|
} else if (previousArea) {
|
||||||
area.w = previousArea.w
|
area.w = previousArea.w
|
||||||
area.h = previousArea.h
|
area.h = previousArea.h
|
||||||
@@ -1691,7 +1944,7 @@ export default {
|
|||||||
this.drawField = null
|
this.drawField = null
|
||||||
this.drawOption = null
|
this.drawOption = null
|
||||||
|
|
||||||
this.selectedAreaRef.value = area
|
this.selectedAreasRef.value = [area]
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
} else {
|
} else {
|
||||||
@@ -1728,8 +1981,8 @@ export default {
|
|||||||
|
|
||||||
if (this.drawFieldType && (area.w === 0 || area.h === 0)) {
|
if (this.drawFieldType && (area.w === 0 || area.h === 0)) {
|
||||||
if (this.selectedField?.type === this.drawFieldType) {
|
if (this.selectedField?.type === this.drawFieldType) {
|
||||||
area.w = this.selectedAreaRef.value.w
|
area.w = this.lastSelectedArea.w
|
||||||
area.h = this.selectedAreaRef.value.h
|
area.h = this.lastSelectedArea.h
|
||||||
} else {
|
} else {
|
||||||
this.setDefaultAreaSize(area, this.drawFieldType)
|
this.setDefaultAreaSize(area, this.drawFieldType)
|
||||||
}
|
}
|
||||||
@@ -1741,7 +1994,7 @@ export default {
|
|||||||
if (area.w && (type !== 'checkbox' || this.drawFieldType || !isTooSmall)) {
|
if (area.w && (type !== 'checkbox' || this.drawFieldType || !isTooSmall)) {
|
||||||
this.addField(type, area)
|
this.addField(type, area)
|
||||||
|
|
||||||
this.selectedAreaRef.value = area
|
this.selectedAreasRef.value = [area]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1821,7 +2074,11 @@ export default {
|
|||||||
|
|
||||||
field.areas.push(fieldArea)
|
field.areas.push(fieldArea)
|
||||||
|
|
||||||
this.selectedAreaRef.value = fieldArea
|
if (this.selectedAreasRef.value.length < 2) {
|
||||||
|
this.selectedAreasRef.value = [fieldArea]
|
||||||
|
} else {
|
||||||
|
this.selectedAreasRef.value.push(fieldArea)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.template.fields.indexOf(field) === -1) {
|
if (this.template.fields.indexOf(field) === -1) {
|
||||||
this.insertField(field)
|
this.insertField(field)
|
||||||
@@ -1834,7 +2091,7 @@ export default {
|
|||||||
if (field.type === 'heading') {
|
if (field.type === 'heading') {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||||
const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === this.selectedAreaRef.value)
|
const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === fieldArea)
|
||||||
|
|
||||||
areaRef.isHeadingSelected = true
|
areaRef.isHeadingSelected = true
|
||||||
|
|
||||||
@@ -1850,7 +2107,7 @@ export default {
|
|||||||
let baseArea
|
let baseArea
|
||||||
|
|
||||||
if (this.selectedField?.type === fieldType) {
|
if (this.selectedField?.type === fieldType) {
|
||||||
baseArea = this.selectedAreaRef.value
|
baseArea = this.lastSelectedArea
|
||||||
} else if (previousField?.areas?.length) {
|
} else if (previousField?.areas?.length) {
|
||||||
baseArea = previousField.areas[previousField.areas.length - 1]
|
baseArea = previousField.areas[previousField.areas.length - 1]
|
||||||
} else {
|
} else {
|
||||||
@@ -2175,7 +2432,7 @@ export default {
|
|||||||
|
|
||||||
documentRef.scrollToArea(area)
|
documentRef.scrollToArea(area)
|
||||||
|
|
||||||
this.selectedAreaRef.value = area
|
this.selectedAreasRef.value = [area]
|
||||||
},
|
},
|
||||||
baseFetch (path, options = {}) {
|
baseFetch (path, options = {}) {
|
||||||
return fetch(this.baseUrl + path, {
|
return fetch(this.baseUrl + path, {
|
||||||
|
|||||||
@@ -168,9 +168,19 @@ export default {
|
|||||||
buildDefaultName: {
|
buildDefaultName: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
withClickSaveEvent: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
excludeFieldUuids: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['close'],
|
emits: ['close', 'click-save'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
conditions: this.item.conditions?.[0] ? JSON.parse(JSON.stringify(this.item.conditions)) : [{}]
|
conditions: this.item.conditions?.[0] ? JSON.parse(JSON.stringify(this.item.conditions)) : [{}]
|
||||||
@@ -183,14 +193,14 @@ export default {
|
|||||||
fields () {
|
fields () {
|
||||||
if (this.item.submitter_uuid) {
|
if (this.item.submitter_uuid) {
|
||||||
return this.template.fields.reduce((acc, f) => {
|
return this.template.fields.reduce((acc, f) => {
|
||||||
if (f !== this.item && !this.excludeTypes.includes(f.type) && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) {
|
if (f !== this.item && !this.excludeTypes.includes(f.type) && !this.excludeFieldUuids.includes(f.uuid) && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) {
|
||||||
acc.push(f)
|
acc.push(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
} else {
|
} else {
|
||||||
return this.template.fields
|
return this.template.fields.filter((f) => !this.excludeFieldUuids.includes(f.uuid))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -234,7 +244,12 @@ export default {
|
|||||||
delete this.item.conditions
|
delete this.item.conditions
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save()
|
if (this.withClickSaveEvent) {
|
||||||
|
this.$emit('click-save')
|
||||||
|
} else {
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
class="my-1 border-base-300"
|
class="my-1 border-base-300"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="showFont"
|
v-if="showFont && !isMultiSelection"
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
||||||
@click.stop="openFontModal"
|
@click.stop="openFontModal"
|
||||||
>
|
>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<span>{{ t('description') }}</span>
|
<span>{{ t('description') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="showCondition"
|
v-if="showCondition && !isMultiSelection"
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
||||||
@click.stop="openConditionModal"
|
@click.stop="openConditionModal"
|
||||||
>
|
>
|
||||||
@@ -73,7 +73,63 @@
|
|||||||
<span>{{ t('formula') }}</span>
|
<span>{{ t('formula') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<hr
|
<hr
|
||||||
v-if="(showFont || showDescription || showCondition || showFormula) && (showCopy || showDelete || showPaste)"
|
v-if="((showFont && !isMultiSelection) || showDescription || (showCondition && !isMultiSelection) || showFormula) && (showCopy || showDelete || showPaste)"
|
||||||
|
class="my-1 border-base-300"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="isMultiSelection"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="$emit('align', 'left')"
|
||||||
|
>
|
||||||
|
<IconLayoutAlignLeft class="w-4 h-4" />
|
||||||
|
<span>{{ t('align_left') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="isMultiSelection"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="$emit('align', 'right')"
|
||||||
|
>
|
||||||
|
<IconLayoutAlignRight class="w-4 h-4" />
|
||||||
|
<span>{{ t('align_right') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="isMultiSelection"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="$emit('align', 'top')"
|
||||||
|
>
|
||||||
|
<IconLayoutAlignTop class="w-4 h-4" />
|
||||||
|
<span>{{ t('align_top') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="isMultiSelection"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="$emit('align', 'bottom')"
|
||||||
|
>
|
||||||
|
<IconLayoutAlignBottom class="w-4 h-4" />
|
||||||
|
<span>{{ t('align_bottom') }}</span>
|
||||||
|
</button>
|
||||||
|
<hr
|
||||||
|
v-if="isMultiSelection && (showFont || showCondition)"
|
||||||
|
class="my-1 border-base-300"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="showFont && isMultiSelection"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="openFontModal"
|
||||||
|
>
|
||||||
|
<IconTypography class="w-4 h-4" />
|
||||||
|
<span>{{ t('font') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="showCondition && isMultiSelection"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="openConditionModal"
|
||||||
|
>
|
||||||
|
<IconRouteAltLeft class="w-4 h-4" />
|
||||||
|
<span>{{ t('condition') }}</span>
|
||||||
|
</button>
|
||||||
|
<hr
|
||||||
|
v-if="isMultiSelection"
|
||||||
class="my-1 border-base-300"
|
class="my-1 border-base-300"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -111,6 +167,24 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="text-xs text-base-content/60 ml-4">{{ isMac ? '⌘V' : 'Ctrl+V' }}</span>
|
<span class="text-xs text-base-content/60 ml-4">{{ isMac ? '⌘V' : 'Ctrl+V' }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="showSelectFields"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center justify-between text-sm"
|
||||||
|
@click.stop="handleToggleSelectMode"
|
||||||
|
>
|
||||||
|
<span class="flex items-center space-x-2">
|
||||||
|
<IconClick
|
||||||
|
v-if="!isSelectModeRef.value"
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
<IconNewSection
|
||||||
|
v-else
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
<span>{{ isSelectModeRef.value ? t('draw_fields') : t('select_fields') }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-base-content/60 ml-4">Tab</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Teleport
|
<Teleport
|
||||||
v-if="isShowFormulaModal"
|
v-if="isShowFormulaModal"
|
||||||
@@ -128,10 +202,12 @@
|
|||||||
to="#docuseal_modal_container"
|
to="#docuseal_modal_container"
|
||||||
>
|
>
|
||||||
<FontModal
|
<FontModal
|
||||||
:field="field"
|
:field="multiSelectField || field"
|
||||||
:area="contextMenu.area"
|
:area="contextMenu.area"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
:with-click-save-event="isMultiSelection"
|
||||||
|
@click-save="handleSaveMultiSelectFontModal"
|
||||||
@close="closeModal"
|
@close="closeModal"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -140,8 +216,11 @@
|
|||||||
to="#docuseal_modal_container"
|
to="#docuseal_modal_container"
|
||||||
>
|
>
|
||||||
<ConditionsModal
|
<ConditionsModal
|
||||||
:item="field"
|
:item="multiSelectField || field"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
:exclude-field-uuids="isMultiSelection ? selectedFields.map(f => f.uuid) : []"
|
||||||
|
:with-click-save-event="isMultiSelection"
|
||||||
|
@click-save="handleSaveMultiSelectConditionsModal"
|
||||||
@close="closeModal"
|
@close="closeModal"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -160,7 +239,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconCopy, IconClipboard, IconTrashX, IconTypography, IconInfoCircle, IconRouteAltLeft, IconMathFunction } from '@tabler/icons-vue'
|
import { IconCopy, IconClipboard, IconTrashX, IconTypography, IconInfoCircle, IconRouteAltLeft, IconMathFunction, IconClick, IconNewSection, IconLayoutAlignLeft, IconLayoutAlignRight, IconLayoutAlignTop, IconLayoutAlignBottom } from '@tabler/icons-vue'
|
||||||
import FormulaModal from './formula_modal'
|
import FormulaModal from './formula_modal'
|
||||||
import FontModal from './font_modal'
|
import FontModal from './font_modal'
|
||||||
import ConditionsModal from './conditions_modal'
|
import ConditionsModal from './conditions_modal'
|
||||||
@@ -178,12 +257,18 @@ export default {
|
|||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconRouteAltLeft,
|
IconRouteAltLeft,
|
||||||
IconMathFunction,
|
IconMathFunction,
|
||||||
|
IconClick,
|
||||||
|
IconNewSection,
|
||||||
|
IconLayoutAlignLeft,
|
||||||
|
IconLayoutAlignRight,
|
||||||
|
IconLayoutAlignTop,
|
||||||
|
IconLayoutAlignBottom,
|
||||||
FormulaModal,
|
FormulaModal,
|
||||||
FontModal,
|
FontModal,
|
||||||
ConditionsModal,
|
ConditionsModal,
|
||||||
DescriptionModal
|
DescriptionModal
|
||||||
},
|
},
|
||||||
inject: ['t', 'save'],
|
inject: ['t', 'save', 'selectedAreasRef', 'isSelectModeRef'],
|
||||||
props: {
|
props: {
|
||||||
contextMenu: {
|
contextMenu: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -197,20 +282,40 @@ export default {
|
|||||||
editable: {
|
editable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
isMultiSelection: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
selectedAreas: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['copy', 'paste', 'delete', 'close'],
|
emits: ['copy', 'paste', 'delete', 'close', 'align'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
isShowFontModal: false,
|
isShowFontModal: false,
|
||||||
isShowConditionsModal: false,
|
isShowConditionsModal: false,
|
||||||
isShowDescriptionModal: false
|
isShowDescriptionModal: false,
|
||||||
|
multiSelectField: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fieldNames: FieldType.computed.fieldNames,
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldLabels: FieldType.computed.fieldLabels,
|
fieldLabels: FieldType.computed.fieldLabels,
|
||||||
|
selectedFields () {
|
||||||
|
if (!this.isMultiSelection) return []
|
||||||
|
|
||||||
|
return this.selectedAreasRef.value.map((area) => {
|
||||||
|
return this.template.fields.find((f) => f.areas?.includes(area))
|
||||||
|
}).filter(Boolean)
|
||||||
|
},
|
||||||
isMac () {
|
isMac () {
|
||||||
return (navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')
|
return (navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')
|
||||||
},
|
},
|
||||||
@@ -236,36 +341,44 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showCopy () {
|
showCopy () {
|
||||||
return !!this.contextMenu.area
|
return !!this.contextMenu.area || this.isMultiSelection
|
||||||
},
|
},
|
||||||
showPaste () {
|
showPaste () {
|
||||||
return !this.contextMenu.area
|
return !this.contextMenu.area && !this.isMultiSelection
|
||||||
},
|
},
|
||||||
showDelete () {
|
showDelete () {
|
||||||
return !!this.contextMenu.area
|
return !!this.contextMenu.area || this.isMultiSelection
|
||||||
},
|
},
|
||||||
showFont () {
|
showFont () {
|
||||||
|
if (this.isMultiSelection) return true
|
||||||
if (!this.field) return false
|
if (!this.field) return false
|
||||||
|
|
||||||
return ['text', 'number', 'date', 'select', 'heading'].includes(this.field.type)
|
return ['text', 'number', 'date', 'select', 'heading'].includes(this.field.type)
|
||||||
},
|
},
|
||||||
showDescription () {
|
showDescription () {
|
||||||
if (!this.field) return false
|
if (!this.field) return false
|
||||||
|
|
||||||
return !['stamp', 'heading', 'strikethrough'].includes(this.field.type)
|
return !['stamp', 'heading', 'strikethrough'].includes(this.field.type)
|
||||||
},
|
},
|
||||||
showCondition () {
|
showCondition () {
|
||||||
|
if (this.isMultiSelection) return true
|
||||||
if (!this.field) return false
|
if (!this.field) return false
|
||||||
|
|
||||||
return !['stamp', 'heading'].includes(this.field.type)
|
return !['stamp', 'heading'].includes(this.field.type)
|
||||||
},
|
},
|
||||||
showFormula () {
|
showFormula () {
|
||||||
if (!this.field) return false
|
if (!this.field) return false
|
||||||
|
|
||||||
return this.field.type === 'number'
|
return this.field.type === 'number'
|
||||||
},
|
},
|
||||||
showRequired () {
|
showRequired () {
|
||||||
if (!this.field) return false
|
if (!this.field) return false
|
||||||
|
|
||||||
return !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
|
return !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
|
||||||
},
|
},
|
||||||
showReadOnly () {
|
showReadOnly () {
|
||||||
if (!this.field) return false
|
if (!this.field) return false
|
||||||
|
|
||||||
return ['text', 'number'].includes(this.field.type)
|
return ['text', 'number'].includes(this.field.type)
|
||||||
},
|
},
|
||||||
isRequired () {
|
isRequired () {
|
||||||
@@ -273,6 +386,9 @@ export default {
|
|||||||
},
|
},
|
||||||
isReadOnly () {
|
isReadOnly () {
|
||||||
return this.field?.readonly || false
|
return this.field?.readonly || false
|
||||||
|
},
|
||||||
|
showSelectFields () {
|
||||||
|
return !this.contextMenu.area && !this.isMultiSelection
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
@@ -333,12 +449,38 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
openFontModal () {
|
openFontModal () {
|
||||||
|
if (this.isMultiSelection) {
|
||||||
|
this.multiSelectField = {
|
||||||
|
name: this.t('fields_selected').replace('{count}', this.selectedFields.length),
|
||||||
|
preferences: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferencesStrings = this.selectedFields.map((f) => JSON.stringify(f.preferences || {}))
|
||||||
|
|
||||||
|
if (preferencesStrings.every((s) => s === preferencesStrings[0])) {
|
||||||
|
this.multiSelectField.preferences = JSON.parse(preferencesStrings[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.isShowFontModal = true
|
this.isShowFontModal = true
|
||||||
},
|
},
|
||||||
openDescriptionModal () {
|
openDescriptionModal () {
|
||||||
this.isShowDescriptionModal = true
|
this.isShowDescriptionModal = true
|
||||||
},
|
},
|
||||||
openConditionModal () {
|
openConditionModal () {
|
||||||
|
if (this.isMultiSelection) {
|
||||||
|
this.multiSelectField = {
|
||||||
|
name: this.t('fields_selected').replace('{count}', this.selectedFields.length),
|
||||||
|
conditions: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionStrings = this.selectedFields.map((f) => JSON.stringify(f.conditions || []))
|
||||||
|
|
||||||
|
if (conditionStrings.every((s) => s === conditionStrings[0])) {
|
||||||
|
this.multiSelectField.conditions = JSON.parse(conditionStrings[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.isShowConditionsModal = true
|
this.isShowConditionsModal = true
|
||||||
},
|
},
|
||||||
openFormulaModal () {
|
openFormulaModal () {
|
||||||
@@ -349,6 +491,30 @@ export default {
|
|||||||
this.isShowFontModal = false
|
this.isShowFontModal = false
|
||||||
this.isShowConditionsModal = false
|
this.isShowConditionsModal = false
|
||||||
this.isShowDescriptionModal = false
|
this.isShowDescriptionModal = false
|
||||||
|
this.multiSelectField = null
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
handleSaveMultiSelectFontModal () {
|
||||||
|
this.selectedFields.forEach((field) => {
|
||||||
|
field.preferences = { ...field.preferences, ...this.multiSelectField.preferences }
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
|
||||||
|
this.closeModal()
|
||||||
|
},
|
||||||
|
handleSaveMultiSelectConditionsModal () {
|
||||||
|
this.selectedFields.forEach((field) => {
|
||||||
|
field.conditions = JSON.parse(JSON.stringify(this.multiSelectField.conditions))
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
|
||||||
|
this.closeModal()
|
||||||
|
},
|
||||||
|
handleToggleSelectMode () {
|
||||||
|
this.isSelectModeRef.value = !this.isSelectModeRef.value
|
||||||
|
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,14 @@
|
|||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
:total-pages="sortedPreviewImages.length"
|
:total-pages="sortedPreviewImages.length"
|
||||||
:image="image"
|
:image="image"
|
||||||
|
:attachment-uuid="document.uuid"
|
||||||
@drop-field="$emit('drop-field', { ...$event, attachment_uuid: document.uuid })"
|
@drop-field="$emit('drop-field', { ...$event, attachment_uuid: document.uuid })"
|
||||||
@remove-area="$emit('remove-area', $event)"
|
@remove-area="$emit('remove-area', $event)"
|
||||||
@copy-field="$emit('copy-field', $event)"
|
@copy-field="$emit('copy-field', $event)"
|
||||||
@paste-field="$emit('paste-field', { ...$event, attachment_uuid: document.uuid })"
|
@paste-field="$emit('paste-field', { ...$event, attachment_uuid: document.uuid })"
|
||||||
|
@copy-selected-areas="$emit('copy-selected-areas')"
|
||||||
|
@delete-selected-areas="$emit('delete-selected-areas')"
|
||||||
|
@align-selected-areas="$emit('align-selected-areas', $event)"
|
||||||
@scroll-to="scrollToArea"
|
@scroll-to="scrollToArea"
|
||||||
@draw="$emit('draw', { area: {...$event.area, attachment_uuid: document.uuid }, isTooSmall: $event.isTooSmall })"
|
@draw="$emit('draw', { area: {...$event.area, attachment_uuid: document.uuid }, isTooSmall: $event.isTooSmall })"
|
||||||
/>
|
/>
|
||||||
@@ -120,7 +124,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['draw', 'drop-field', 'remove-area', 'paste-field', 'copy-field'],
|
emits: ['draw', 'drop-field', 'remove-area', 'paste-field', 'copy-field', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
pageRefs: []
|
pageRefs: []
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ export default {
|
|||||||
IconMathFunction,
|
IconMathFunction,
|
||||||
FieldType
|
FieldType
|
||||||
},
|
},
|
||||||
inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef', 't', 'locale'],
|
inject: ['template', 'save', 'backgroundColor', 'selectedAreasRef', 't', 'locale'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -451,7 +451,7 @@ export default {
|
|||||||
const area = this.field.areas.find((a) => a.option_uuid === option.uuid)
|
const area = this.field.areas.find((a) => a.option_uuid === option.uuid)
|
||||||
|
|
||||||
if (area) {
|
if (area) {
|
||||||
this.selectedAreaRef.value = area
|
this.selectedAreasRef.value = [area]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollToFirstArea () {
|
scrollToFirstArea () {
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ export default {
|
|||||||
IconDrag,
|
IconDrag,
|
||||||
IconLock
|
IconLock
|
||||||
},
|
},
|
||||||
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withKba', 'withPayment', 't', 'fieldsDragFieldRef', 'baseFetch'],
|
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withKba', 'withPayment', 't', 'fieldsDragFieldRef', 'baseFetch', 'selectedAreasRef'],
|
||||||
props: {
|
props: {
|
||||||
fields: {
|
fields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -610,6 +610,10 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
field.areas?.forEach((area) => {
|
||||||
|
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(area), 1)
|
||||||
|
})
|
||||||
|
|
||||||
if (save) {
|
if (save) {
|
||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,12 +212,17 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
withClickSaveEvent: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
buildDefaultName: {
|
buildDefaultName: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['close'],
|
emits: ['close', 'click-save'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
preferences: {}
|
preferences: {}
|
||||||
@@ -322,7 +327,11 @@ export default {
|
|||||||
|
|
||||||
Object.assign(this.field.preferences, this.preferences)
|
Object.assign(this.field.preferences, this.preferences)
|
||||||
|
|
||||||
this.save()
|
if (this.withClickSaveEvent) {
|
||||||
|
this.$emit('click-save')
|
||||||
|
} else {
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,7 +187,14 @@ const en = {
|
|||||||
sync: 'Sync',
|
sync: 'Sync',
|
||||||
syncing: 'Syncing...',
|
syncing: 'Syncing...',
|
||||||
copy: 'Copy',
|
copy: 'Copy',
|
||||||
paste: 'Paste'
|
paste: 'Paste',
|
||||||
|
select_fields: 'Select Fields',
|
||||||
|
draw_fields: 'Draw Fields',
|
||||||
|
align_left: 'Align Left',
|
||||||
|
align_right: 'Align Right',
|
||||||
|
align_top: 'Align Top',
|
||||||
|
align_bottom: 'Align Bottom',
|
||||||
|
fields_selected: '{count} Fields Selected'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
@@ -379,7 +386,14 @@ const es = {
|
|||||||
sync: 'Sincronizar',
|
sync: 'Sincronizar',
|
||||||
syncing: 'Sincronizando...',
|
syncing: 'Sincronizando...',
|
||||||
copy: 'Copiar',
|
copy: 'Copiar',
|
||||||
paste: 'Pegar'
|
paste: 'Pegar',
|
||||||
|
select_fields: 'Seleccionar Campos',
|
||||||
|
draw_fields: 'Dibujar Campos',
|
||||||
|
align_left: 'Alinear a la izquierda',
|
||||||
|
align_right: 'Alinear a la derecha',
|
||||||
|
align_top: 'Alinear arriba',
|
||||||
|
align_bottom: 'Alinear abajo',
|
||||||
|
fields_selected: '{count} Campos Seleccionados'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
@@ -571,7 +585,14 @@ const it = {
|
|||||||
sync: 'Sincronizza',
|
sync: 'Sincronizza',
|
||||||
syncing: 'Sincronizzazione...',
|
syncing: 'Sincronizzazione...',
|
||||||
copy: 'Copia',
|
copy: 'Copia',
|
||||||
paste: 'Incolla'
|
paste: 'Incolla',
|
||||||
|
select_fields: 'Seleziona Campi',
|
||||||
|
draw_fields: 'Disegna Campi',
|
||||||
|
align_left: 'Allinea a sinistra',
|
||||||
|
align_right: 'Allinea a destra',
|
||||||
|
align_top: 'Allinea in alto',
|
||||||
|
align_bottom: 'Allinea in basso',
|
||||||
|
fields_selected: '{count} Campi Selezionati'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
@@ -763,7 +784,14 @@ const pt = {
|
|||||||
sync: 'Sincronizar',
|
sync: 'Sincronizar',
|
||||||
syncing: 'Sincronizando...',
|
syncing: 'Sincronizando...',
|
||||||
copy: 'Copiar',
|
copy: 'Copiar',
|
||||||
paste: 'Colar'
|
paste: 'Colar',
|
||||||
|
select_fields: 'Selecionar Campos',
|
||||||
|
draw_fields: 'Desenhar Campos',
|
||||||
|
align_left: 'Alinhar à esquerda',
|
||||||
|
align_right: 'Alinhar à direita',
|
||||||
|
align_top: 'Alinhar ao topo',
|
||||||
|
align_bottom: 'Alinhar à parte inferior',
|
||||||
|
fields_selected: '{count} Campos Selecionados'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
@@ -955,7 +983,14 @@ const fr = {
|
|||||||
sync: 'Synchroniser',
|
sync: 'Synchroniser',
|
||||||
syncing: 'Synchronisation...',
|
syncing: 'Synchronisation...',
|
||||||
copy: 'Copier',
|
copy: 'Copier',
|
||||||
paste: 'Coller'
|
paste: 'Coller',
|
||||||
|
select_fields: 'Sélectionner Champs',
|
||||||
|
draw_fields: 'Dessiner Champs',
|
||||||
|
align_left: 'Aligner à gauche',
|
||||||
|
align_right: 'Aligner à droite',
|
||||||
|
align_top: 'Aligner en haut',
|
||||||
|
align_bottom: 'Aligner en bas',
|
||||||
|
fields_selected: '{count} Champs Sélectionnés'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
@@ -1147,7 +1182,14 @@ const de = {
|
|||||||
sync: 'Synchronisieren',
|
sync: 'Synchronisieren',
|
||||||
syncing: 'Synchronisiere...',
|
syncing: 'Synchronisiere...',
|
||||||
copy: 'Kopieren',
|
copy: 'Kopieren',
|
||||||
paste: 'Einfügen'
|
paste: 'Einfügen',
|
||||||
|
select_fields: 'Felder Auswählen',
|
||||||
|
draw_fields: 'Felder Zeichnen',
|
||||||
|
align_left: 'Links ausrichten',
|
||||||
|
align_right: 'Rechts ausrichten',
|
||||||
|
align_top: 'Oben ausrichten',
|
||||||
|
align_bottom: 'Unten ausrichten',
|
||||||
|
fields_selected: '{count} Felder Ausgewählt'
|
||||||
}
|
}
|
||||||
|
|
||||||
const nl = {
|
const nl = {
|
||||||
@@ -1339,7 +1381,14 @@ const nl = {
|
|||||||
sync: 'Synchroniseren',
|
sync: 'Synchroniseren',
|
||||||
syncing: 'Synchroniseren...',
|
syncing: 'Synchroniseren...',
|
||||||
copy: 'Kopiëren',
|
copy: 'Kopiëren',
|
||||||
paste: 'Plakken'
|
paste: 'Plakken',
|
||||||
|
select_fields: 'Velden Selecteren',
|
||||||
|
draw_fields: 'Velden Tekenen',
|
||||||
|
align_left: 'Links uitlijnen',
|
||||||
|
align_right: 'Rechts uitlijnen',
|
||||||
|
align_top: 'Boven uitlijnen',
|
||||||
|
align_bottom: 'Onder uitlijnen',
|
||||||
|
fields_selected: '{count} Velden Geselecteerd'
|
||||||
}
|
}
|
||||||
|
|
||||||
export { en, es, it, pt, fr, de, nl }
|
export { en, es, it, pt, fr, de, nl }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative select-none mb-4 before:border before:rounded before:top-0 before:bottom-0 before:left-0 before:right-0 before:absolute"
|
class="relative select-none mb-4 before:border before:rounded before:top-0 before:bottom-0 before:left-0 before:right-0 before:absolute"
|
||||||
:class="{ 'cursor-crosshair': allowDraw && editable, 'touch-none': !!drawField }"
|
:class="{ 'cursor-crosshair': allowDraw && editable && !isSelectMode, 'touch-none': !!drawField }"
|
||||||
style="container-type: size"
|
style="container-type: size"
|
||||||
:style="{ aspectRatio: `${width} / ${height}`}"
|
:style="{ aspectRatio: `${width} / ${height}`}"
|
||||||
>
|
>
|
||||||
@@ -19,6 +19,18 @@
|
|||||||
@pointerdown="onStartDraw"
|
@pointerdown="onStartDraw"
|
||||||
@contextmenu="openContextMenu"
|
@contextmenu="openContextMenu"
|
||||||
>
|
>
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
v-for="(item, i) in areas"
|
v-for="(item, i) in areas"
|
||||||
:key="i"
|
:key="i"
|
||||||
@@ -35,6 +47,7 @@
|
|||||||
:default-field="defaultFieldsIndex[item.field.name]"
|
:default-field="defaultFieldsIndex[item.field.name]"
|
||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
:max-page="totalPages - 1"
|
:max-page="totalPages - 1"
|
||||||
|
:is-select-mode="isSelectMode"
|
||||||
@start-resize="resizeDirection = $event"
|
@start-resize="resizeDirection = $event"
|
||||||
@stop-resize="resizeDirection = null"
|
@stop-resize="resizeDirection = null"
|
||||||
@remove="$emit('remove-area', item.area)"
|
@remove="$emit('remove-area', item.area)"
|
||||||
@@ -49,6 +62,11 @@
|
|||||||
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || dragFieldPlaceholder?.type || defaultFieldType }"
|
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || dragFieldPlaceholder?.type || defaultFieldType }"
|
||||||
:area="newArea"
|
:area="newArea"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="selectionRect"
|
||||||
|
class="absolute outline-dashed outline-gray-400 pointer-events-none z-20"
|
||||||
|
:style="selectionRectStyle"
|
||||||
|
/>
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
v-if="contextMenu"
|
v-if="contextMenu"
|
||||||
:context-menu="contextMenu"
|
:context-menu="contextMenu"
|
||||||
@@ -59,13 +77,25 @@
|
|||||||
@paste="handlePaste"
|
@paste="handlePaste"
|
||||||
@close="closeContextMenu"
|
@close="closeContextMenu"
|
||||||
/>
|
/>
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="resizeDirection || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value"
|
v-show="resizeDirection || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value || selectionRect"
|
||||||
id="mask"
|
id="mask"
|
||||||
ref="mask"
|
ref="mask"
|
||||||
class="top-0 bottom-0 left-0 right-0 absolute"
|
class="top-0 bottom-0 left-0 right-0 absolute"
|
||||||
:class="{ 'z-10': !isMobile, 'cursor-grab': isDrag, 'cursor-nwse-resize': drawField, [resizeDirectionClasses[resizeDirection]]: !!resizeDirectionClasses }"
|
:class="{ 'z-10': !isMobile, 'cursor-grab': isDrag, 'cursor-nwse-resize': drawField && !isSelectMode, [resizeDirectionClasses[resizeDirection]]: !!resizeDirectionClasses }"
|
||||||
@pointermove="onPointermove"
|
@pointermove="onPointermove"
|
||||||
@pointerdown="onStartDraw"
|
@pointerdown="onStartDraw"
|
||||||
@contextmenu="openContextMenu"
|
@contextmenu="openContextMenu"
|
||||||
@@ -81,14 +111,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import FieldArea from './area'
|
import FieldArea from './area'
|
||||||
import ContextMenu from './context_menu'
|
import ContextMenu from './context_menu'
|
||||||
|
import SelectionBox from './selection_box'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplatePage',
|
name: 'TemplatePage',
|
||||||
components: {
|
components: {
|
||||||
FieldArea,
|
FieldArea,
|
||||||
ContextMenu
|
ContextMenu,
|
||||||
|
SelectionBox
|
||||||
},
|
},
|
||||||
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'assignDropAreaSize', 'selectedAreaRef'],
|
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'assignDropAreaSize', 'selectedAreasRef', 'template', 'isSelectModeRef'],
|
||||||
props: {
|
props: {
|
||||||
image: {
|
image: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -170,19 +202,64 @@ export default {
|
|||||||
number: {
|
number: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
attachmentUuid: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['draw', 'drop-field', 'remove-area', 'copy-field', 'paste-field', 'scroll-to'],
|
emits: ['draw', 'drop-field', 'remove-area', 'copy-field', 'paste-field', 'scroll-to', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
areaRefs: [],
|
areaRefs: [],
|
||||||
showMask: false,
|
showMask: false,
|
||||||
resizeDirection: null,
|
resizeDirection: null,
|
||||||
newArea: null,
|
newArea: null,
|
||||||
contextMenu: null
|
contextMenu: null,
|
||||||
|
selectionRect: null,
|
||||||
|
selectionContextMenu: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isSelectMode () {
|
||||||
|
return this.isSelectModeRef.value && !this.drawFieldType && this.editable
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
},
|
||||||
defaultFieldsIndex () {
|
defaultFieldsIndex () {
|
||||||
return this.defaultFields.reduce((acc, field) => {
|
return this.defaultFields.reduce((acc, field) => {
|
||||||
acc[field.name] = field
|
acc[field.name] = field
|
||||||
@@ -217,6 +294,16 @@ export default {
|
|||||||
},
|
},
|
||||||
height () {
|
height () {
|
||||||
return this.image.metadata.height
|
return this.image.metadata.height
|
||||||
|
},
|
||||||
|
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 + '%'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
@@ -269,6 +356,31 @@ export default {
|
|||||||
field
|
field
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
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()
|
||||||
|
},
|
||||||
closeContextMenu () {
|
closeContextMenu () {
|
||||||
this.contextMenu = null
|
this.contextMenu = null
|
||||||
this.newArea = null
|
this.newArea = null
|
||||||
@@ -276,7 +388,7 @@ export default {
|
|||||||
},
|
},
|
||||||
handleCopy () {
|
handleCopy () {
|
||||||
if (this.contextMenu.area) {
|
if (this.contextMenu.area) {
|
||||||
this.selectedAreaRef.value = this.contextMenu.area
|
this.selectedAreasRef.value = [this.contextMenu.area]
|
||||||
|
|
||||||
this.$emit('copy-field')
|
this.$emit('copy-field')
|
||||||
}
|
}
|
||||||
@@ -338,6 +450,16 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.selectedAreasRef.value.length >= 2) {
|
||||||
|
this.selectedAreasRef.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isSelectMode) {
|
||||||
|
this.startSelectionRect(e)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.allowDraw) {
|
if (!this.allowDraw) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -363,7 +485,69 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
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
|
||||||
|
})
|
||||||
|
},
|
||||||
onPointermove (e) {
|
onPointermove (e) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if (this.newArea) {
|
if (this.newArea) {
|
||||||
const dx = e.offsetX / this.$refs.mask.clientWidth - this.newArea.initialX
|
const dx = e.offsetX / this.$refs.mask.clientWidth - this.newArea.initialX
|
||||||
const dy = e.offsetY / this.$refs.mask.clientHeight - this.newArea.initialY
|
const dy = e.offsetY / this.$refs.mask.clientHeight - this.newArea.initialY
|
||||||
@@ -389,7 +573,20 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPointerup (e) {
|
onPointerup (e) {
|
||||||
if (this.newArea) {
|
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
|
||||||
|
} else if (this.newArea) {
|
||||||
const area = {
|
const area = {
|
||||||
x: this.newArea.x,
|
x: this.newArea.x,
|
||||||
y: this.newArea.y,
|
y: this.newArea.y,
|
||||||
@@ -412,6 +609,14 @@ export default {
|
|||||||
|
|
||||||
this.showMask = false
|
this.showMask = false
|
||||||
this.newArea = null
|
this.newArea = null
|
||||||
|
},
|
||||||
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="absolute outline-dashed outline-gray-400 cursor-move"
|
||||||
|
:class="[isResizing || isCmdKeyRef.value ? 'z-0' : 'z-20', { 'pointer-events-none': isDrawing || fieldsDragFieldRef.value || isDrag }]"
|
||||||
|
:style="positionStyle"
|
||||||
|
@pointerdown.stop.prevent="onPointerDown"
|
||||||
|
@contextmenu.stop.prevent="openContextMenu"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SelectionBox',
|
||||||
|
inject: ['save', 'selectedAreasRef', 'isCmdKeyRef', 'fieldsDragFieldRef'],
|
||||||
|
props: {
|
||||||
|
selectionBox: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
pageWidth: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
pageHeight: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isResizing: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isDrag: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isDrawing: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['move', 'contextmenu', 'close-context-menu'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isDragging: false,
|
||||||
|
dragStart: { x: 0, y: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
positionStyle () {
|
||||||
|
const { x, y, w, h } = this.selectionBox
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: y * 100 + '%',
|
||||||
|
left: x * 100 + '%',
|
||||||
|
width: w * 100 + '%',
|
||||||
|
height: h * 100 + '%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openContextMenu (e) {
|
||||||
|
this.$emit('contextmenu', e)
|
||||||
|
},
|
||||||
|
onPointerDown (e) {
|
||||||
|
this.$emit('close-context-menu')
|
||||||
|
|
||||||
|
this.startDrag(e)
|
||||||
|
},
|
||||||
|
startDrag (e) {
|
||||||
|
this.isDragging = true
|
||||||
|
this.dragStart = { x: e.clientX, y: e.clientY }
|
||||||
|
|
||||||
|
document.addEventListener('pointermove', this.onDrag)
|
||||||
|
document.addEventListener('pointerup', this.stopDrag)
|
||||||
|
},
|
||||||
|
onDrag (e) {
|
||||||
|
if (!this.isDragging) return
|
||||||
|
|
||||||
|
const parent = this.$el.parentElement
|
||||||
|
|
||||||
|
const rect = parent.getBoundingClientRect()
|
||||||
|
|
||||||
|
const dx = (e.clientX - this.dragStart.x) / rect.width
|
||||||
|
const dy = (e.clientY - this.dragStart.y) / rect.height
|
||||||
|
|
||||||
|
this.$emit('move', dx, dy)
|
||||||
|
|
||||||
|
this.dragStart = { x: e.clientX, y: e.clientY }
|
||||||
|
},
|
||||||
|
stopDrag () {
|
||||||
|
this.isDragging = false
|
||||||
|
|
||||||
|
document.removeEventListener('pointermove', this.onDrag)
|
||||||
|
document.removeEventListener('pointerup', this.stopDrag)
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user