add context menu to template builder

pull/572/head
Alex Turchyn 2 months ago committed by GitHub
parent 092a19966c
commit bf53204a41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -376,6 +376,8 @@
@draw="[onDraw($event), withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]" @draw="[onDraw($event), withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
@drop-field="onDropfield" @drop-field="onDropfield"
@remove-area="removeArea" @remove-area="removeArea"
@paste-field="pasteField"
@copy-field="copyField"
/> />
<DocumentControls <DocumentControls
v-if="isBreakpointLg && editable" v-if="isBreakpointLg && editable"
@ -852,7 +854,6 @@ export default {
showDrawField: false, showDrawField: false,
pendingFieldAttachmentUuids: [], pendingFieldAttachmentUuids: [],
drawField: null, drawField: null,
copiedArea: null,
drawFieldType: null, drawFieldType: null,
drawOption: null, drawOption: null,
dragField: null, dragField: null,
@ -1414,8 +1415,8 @@ export default {
} 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() event.preventDefault()
this.copiedArea = this.selectedAreaRef?.value this.copyField()
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v' && this.copiedArea && 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()
@ -1497,47 +1498,116 @@ export default {
} }
}) })
}, },
pasteField () { copyField () {
const field = this.template.fields.find((f) => f.areas?.includes(this.copiedArea)) const area = this.selectedAreaRef.value
const currentArea = this.selectedAreaRef?.value || this.copiedArea
if (field && currentArea) { if (!area) return
const area = {
...JSON.parse(JSON.stringify(this.copiedArea)),
attachment_uuid: currentArea.attachment_uuid,
page: currentArea.page,
x: currentArea.x,
y: currentArea.y + currentArea.h * 1.3
}
if (['radio', 'multiple'].includes(field.type)) { const field = this.template.fields.find((f) => f.areas?.includes(area))
this.copiedArea.option_uuid ||= field.options[0].uuid
area.option_uuid = v4()
const lastOption = field.options[field.options.length - 1] if (!field) return
if (!field.areas.find((a) => lastOption.uuid === a.option_uuid)) { const clipboardData = {
area.option_uuid = lastOption.uuid field: JSON.parse(JSON.stringify(field)),
} else { area: JSON.parse(JSON.stringify(area)),
field.options.push({ uuid: area.option_uuid }) templateId: this.template.id,
} timestamp: Date.now()
}
delete clipboardData.field.areas
delete clipboardData.field.uuid
delete clipboardData.field.submitter_uuid
try {
localStorage.setItem('docuseal_clipboard', JSON.stringify(clipboardData))
} catch (e) {
console.error('Failed to save clipboard:', e)
}
},
pasteField (targetPosition = null) {
let field = null
let area = null
let isSameTemplate = false
const clipboard = localStorage.getItem('docuseal_clipboard')
if (clipboard) {
const data = JSON.parse(clipboard)
field.areas.push(area) if (Date.now() - data.timestamp < 3600000) {
field = data.field
area = data.area
isSameTemplate = data.templateId === this.template.id
} else { } else {
const newField = { localStorage.removeItem('docuseal_clipboard')
...JSON.parse(JSON.stringify(field)), }
uuid: v4(), }
areas: [area]
} if (!field || !area) return
if (!isSameTemplate) {
delete field.conditions
delete field.preferences?.formula
}
const currentArea = this.selectedAreaRef.value
const defaultAttachmentUuid = this.template.schema[0]?.attachment_uuid
this.insertField(newField) if (field && currentArea) {
const attachmentUuid = targetPosition?.attachment_uuid ||
(this.template.documents.find((d) => d.uuid === currentArea.attachment_uuid) ? currentArea.attachment_uuid : null) ||
defaultAttachmentUuid
const newArea = {
...JSON.parse(JSON.stringify(area)),
attachment_uuid: attachmentUuid,
page: targetPosition?.page ?? (attachmentUuid === currentArea.attachment_uuid ? currentArea.page : 0),
x: targetPosition ? (targetPosition.x - area.w / 2) : currentArea.x,
y: targetPosition ? (targetPosition.y - area.h / 2) : (currentArea.y + currentArea.h * 1.3)
} }
this.selectedAreaRef.value = area const newField = {
...JSON.parse(JSON.stringify(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)
this.selectedAreaRef.value = newArea
this.save() this.save()
} }
}, },
hasClipboardData () {
try {
const clipboard = localStorage.getItem('docuseal_clipboard')
if (clipboard) {
const data = JSON.parse(clipboard)
return Date.now() - data.timestamp < 3600000
}
return false
} catch {
return false
}
},
pushUndo () { pushUndo () {
const stringData = JSON.stringify(this.template) const stringData = JSON.stringify(this.template)

@ -0,0 +1,357 @@
<template>
<div>
<div
v-if="!isShowFormulaModal && !isShowFontModal && !isShowConditionsModal && !isShowDescriptionModal"
ref="menu"
class="fixed z-50 p-1 bg-white shadow-lg rounded-lg border border-base-300 min-w-[170px] cursor-default"
:style="menuStyle"
@mousedown.stop
@pointerdown.stop
>
<label
v-if="showRequired"
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm cursor-pointer"
@click.stop
>
<input
:checked="isRequired"
type="checkbox"
class="toggle toggle-xs"
@change="handleToggleRequired($event.target.checked)"
@click.stop
>
<span>{{ t('required') }}</span>
</label>
<label
v-if="showReadOnly"
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm cursor-pointer"
@click.stop
>
<input
:checked="isReadOnly"
type="checkbox"
class="toggle toggle-xs"
@change="handleToggleReadOnly($event.target.checked)"
@click.stop
>
<span>{{ t('read_only') }}</span>
</label>
<hr
v-if="(showRequired || showReadOnly) && (showFont || showDescription || showCondition || showFormula)"
class="my-1 border-base-300"
>
<button
v-if="showFont"
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="showDescription"
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
@click.stop="openDescriptionModal"
>
<IconInfoCircle class="w-4 h-4" />
<span>{{ t('description') }}</span>
</button>
<button
v-if="showCondition"
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>
<button
v-if="showFormula"
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
@click.stop="openFormulaModal"
>
<IconMathFunction class="w-4 h-4" />
<span>{{ t('formula') }}</span>
</button>
<hr
v-if="(showFont || showDescription || showCondition || showFormula) && (showCopy || showDelete || showPaste)"
class="my-1 border-base-300"
>
<button
v-if="showCopy"
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center justify-between text-sm"
@click.stop="$emit('copy')"
>
<span class="flex items-center space-x-2">
<IconCopy class="w-4 h-4" />
<span>{{ t('copy') }}</span>
</span>
<span class="text-xs text-base-content/60 ml-4">{{ isMac ? '⌘C' : 'Ctrl+C' }}</span>
</button>
<button
v-if="showDelete"
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center justify-between text-sm text-red-600"
@click.stop="$emit('delete')"
>
<span class="flex items-center space-x-2">
<IconTrashX class="w-4 h-4" />
<span>{{ t('remove') }}</span>
</span>
<span class="text-xs text-base-content/60 ml-4">Del</span>
</button>
<button
v-if="showPaste"
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center justify-between text-sm"
:class="!hasClipboardData ? 'opacity-50 cursor-not-allowed' : 'hover:bg-base-100'"
:disabled="!hasClipboardData"
@click.stop="!hasClipboardData ? null : $emit('paste')"
>
<span class="flex items-center space-x-2">
<IconClipboard class="w-4 h-4" />
<span>{{ t('paste') }}</span>
</span>
<span class="text-xs text-base-content/60 ml-4">{{ isMac ? '⌘V' : 'Ctrl+V' }}</span>
</button>
</div>
<Teleport
v-if="isShowFormulaModal"
to="#docuseal_modal_container"
>
<FormulaModal
:field="field"
:editable="editable"
:build-default-name="buildDefaultName"
@close="closeModal"
/>
</Teleport>
<Teleport
v-if="isShowFontModal"
to="#docuseal_modal_container"
>
<FontModal
:field="field"
:area="contextMenu.area"
:editable="editable"
:build-default-name="buildDefaultName"
@close="closeModal"
/>
</Teleport>
<Teleport
v-if="isShowConditionsModal"
to="#docuseal_modal_container"
>
<ConditionsModal
:item="field"
:build-default-name="buildDefaultName"
@close="closeModal"
/>
</Teleport>
<Teleport
v-if="isShowDescriptionModal"
to="#docuseal_modal_container"
>
<DescriptionModal
:field="field"
:editable="editable"
:build-default-name="buildDefaultName"
@close="closeModal"
/>
</Teleport>
</div>
</template>
<script>
import { IconCopy, IconClipboard, IconTrashX, IconTypography, IconInfoCircle, IconRouteAltLeft, IconMathFunction } from '@tabler/icons-vue'
import FormulaModal from './formula_modal'
import FontModal from './font_modal'
import ConditionsModal from './conditions_modal'
import DescriptionModal from './description_modal'
import Field from './field'
import FieldType from './field_type.vue'
export default {
name: 'ContextMenu',
components: {
IconCopy,
IconClipboard,
IconTrashX,
IconTypography,
IconInfoCircle,
IconRouteAltLeft,
IconMathFunction,
FormulaModal,
FontModal,
ConditionsModal,
DescriptionModal
},
inject: ['t', 'save'],
props: {
contextMenu: {
type: Object,
default: null,
required: true
},
field: {
type: Object,
default: null
},
editable: {
type: Boolean,
default: true
}
},
emits: ['copy', 'paste', 'delete', 'close'],
data () {
return {
isShowFormulaModal: false,
isShowFontModal: false,
isShowConditionsModal: false,
isShowDescriptionModal: false
}
},
computed: {
fieldNames: FieldType.computed.fieldNames,
fieldLabels: FieldType.computed.fieldLabels,
isMac () {
return (navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')
},
hasClipboardData () {
try {
const clipboard = localStorage.getItem('docuseal_clipboard')
if (clipboard) {
const data = JSON.parse(clipboard)
return Date.now() - data.timestamp < 3600000
}
return false
} catch {
return false
}
},
menuStyle () {
return {
left: this.contextMenu.x + 'px',
top: this.contextMenu.y + 'px'
}
},
showCopy () {
return !!this.contextMenu.area
},
showPaste () {
return !this.contextMenu.area
},
showDelete () {
return !!this.contextMenu.area
},
showFont () {
if (!this.field) return false
return ['text', 'number', 'date', 'select', 'heading'].includes(this.field.type)
},
showDescription () {
if (!this.field) return false
return !['stamp', 'heading', 'strikethrough'].includes(this.field.type)
},
showCondition () {
if (!this.field) return false
return !['stamp', 'heading'].includes(this.field.type)
},
showFormula () {
if (!this.field) return false
return this.field.type === 'number'
},
showRequired () {
if (!this.field) return false
return !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
},
showReadOnly () {
if (!this.field) return false
return ['text', 'number'].includes(this.field.type)
},
isRequired () {
return this.field?.required || false
},
isReadOnly () {
return this.field?.readonly || false
}
},
mounted () {
document.addEventListener('keydown', this.onKeyDown)
document.addEventListener('mousedown', this.handleClickOutside)
this.$nextTick(() => {
this.checkMenuPosition()
})
},
beforeUnmount () {
document.removeEventListener('keydown', this.onKeyDown)
document.removeEventListener('mousedown', this.handleClickOutside)
},
methods: {
buildDefaultName: Field.methods.buildDefaultName,
checkMenuPosition () {
if (this.$refs.menu) {
const rect = this.$refs.menu.getBoundingClientRect()
if (rect.bottom > window.innerHeight) {
this.contextMenu.y = this.contextMenu.y - rect.height
}
if (rect.right > window.innerWidth) {
this.contextMenu.x = this.contextMenu.x - rect.width
}
}
},
handleToggleRequired (value) {
if (this.field) {
this.field.required = value
this.save()
}
},
handleToggleReadOnly (value) {
if (this.field) {
this.field.readonly = value
this.save()
}
},
onKeyDown (event) {
if (event.key === 'Escape') {
event.preventDefault()
event.stopPropagation()
this.$emit('close')
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v' && this.hasClipboardData) {
event.preventDefault()
event.stopPropagation()
this.$emit('paste')
}
},
handleClickOutside (event) {
if (this.$refs.menu && !this.$refs.menu.contains(event.target)) {
this.$emit('close')
}
},
openFontModal () {
this.isShowFontModal = true
},
openDescriptionModal () {
this.isShowDescriptionModal = true
},
openConditionModal () {
this.isShowConditionsModal = true
},
openFormulaModal () {
this.isShowFormulaModal = true
},
closeModal () {
this.isShowFormulaModal = false
this.isShowFontModal = false
this.isShowConditionsModal = false
this.isShowDescriptionModal = false
this.$emit('close')
}
}
}
</script>

@ -22,8 +22,10 @@
:selected-submitter="selectedSubmitter" :selected-submitter="selectedSubmitter"
:total-pages="sortedPreviewImages.length" :total-pages="sortedPreviewImages.length"
:image="image" :image="image"
@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)"
@paste-field="$emit('paste-field', { ...$event, attachment_uuid: document.uuid })"
@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 })"
/> />
@ -118,7 +120,7 @@ export default {
default: false default: false
} }
}, },
emits: ['draw', 'drop-field', 'remove-area'], emits: ['draw', 'drop-field', 'remove-area', 'paste-field', 'copy-field'],
data () { data () {
return { return {
pageRefs: [] pageRefs: []

@ -185,7 +185,9 @@ const en = {
start_tour: 'Start Tour', start_tour: 'Start Tour',
or_add_from: 'Or add from', or_add_from: 'Or add from',
sync: 'Sync', sync: 'Sync',
syncing: 'Syncing...' syncing: 'Syncing...',
copy: 'Copy',
paste: 'Paste'
} }
const es = { const es = {
@ -375,7 +377,9 @@ const es = {
start_tour: 'Iniciar guía', start_tour: 'Iniciar guía',
or_add_from: 'O agregar desde', or_add_from: 'O agregar desde',
sync: 'Sincronizar', sync: 'Sincronizar',
syncing: 'Sincronizando...' syncing: 'Sincronizando...',
copy: 'Copiar',
paste: 'Pegar'
} }
const it = { const it = {
@ -565,7 +569,9 @@ const it = {
start_tour: 'Inizia il tour', start_tour: 'Inizia il tour',
or_add_from: 'O aggiungi da', or_add_from: 'O aggiungi da',
sync: 'Sincronizza', sync: 'Sincronizza',
syncing: 'Sincronizzazione...' syncing: 'Sincronizzazione...',
copy: 'Copia',
paste: 'Incolla'
} }
const pt = { const pt = {
@ -755,7 +761,9 @@ const pt = {
start_tour: 'Iniciar tour', start_tour: 'Iniciar tour',
or_add_from: 'Ou adicionar de', or_add_from: 'Ou adicionar de',
sync: 'Sincronizar', sync: 'Sincronizar',
syncing: 'Sincronizando...' syncing: 'Sincronizando...',
copy: 'Copiar',
paste: 'Colar'
} }
const fr = { const fr = {
@ -945,7 +953,9 @@ const fr = {
start_tour: 'Démarrer', start_tour: 'Démarrer',
or_add_from: 'Ou ajouter depuis', or_add_from: 'Ou ajouter depuis',
sync: 'Synchroniser', sync: 'Synchroniser',
syncing: 'Synchronisation...' syncing: 'Synchronisation...',
copy: 'Copier',
paste: 'Coller'
} }
const de = { const de = {
@ -1135,7 +1145,9 @@ const de = {
start_tour: 'Tour starten', start_tour: 'Tour starten',
or_add_from: 'Oder hinzufügen aus', or_add_from: 'Oder hinzufügen aus',
sync: 'Synchronisieren', sync: 'Synchronisieren',
syncing: 'Synchronisiere...' syncing: 'Synchronisiere...',
copy: 'Kopieren',
paste: 'Einfügen'
} }
const nl = { const nl = {
@ -1325,7 +1337,9 @@ const nl = {
start_tour: 'Rondleiding starten', start_tour: 'Rondleiding starten',
or_add_from: 'Of toevoegen van', or_add_from: 'Of toevoegen van',
sync: 'Synchroniseren', sync: 'Synchroniseren',
syncing: 'Synchroniseren...' syncing: 'Synchroniseren...',
copy: 'Kopiëren',
paste: 'Plakken'
} }
export { en, es, it, pt, fr, de, nl } export { en, es, it, pt, fr, de, nl }

@ -17,6 +17,7 @@
<div <div
class="top-0 bottom-0 left-0 right-0 absolute" class="top-0 bottom-0 left-0 right-0 absolute"
@pointerdown="onStartDraw" @pointerdown="onStartDraw"
@contextmenu="openContextMenu"
> >
<FieldArea <FieldArea
v-for="(item, i) in areas" v-for="(item, i) in areas"
@ -38,6 +39,7 @@
@stop-resize="resizeDirection = null" @stop-resize="resizeDirection = null"
@remove="$emit('remove-area', item.area)" @remove="$emit('remove-area', item.area)"
@scroll-to="$emit('scroll-to', $event)" @scroll-to="$emit('scroll-to', $event)"
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
/> />
<FieldArea <FieldArea
v-if="newArea" v-if="newArea"
@ -47,6 +49,16 @@
: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"
/> />
<ContextMenu
v-if="contextMenu"
:context-menu="contextMenu"
:field="contextMenu.field"
:editable="editable"
@copy="handleCopy"
@delete="handleDelete"
@paste="handlePaste"
@close="closeContextMenu"
/>
</div> </div>
<div <div
v-show="resizeDirection || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value" v-show="resizeDirection || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value"
@ -56,6 +68,7 @@
: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, [resizeDirectionClasses[resizeDirection]]: !!resizeDirectionClasses }"
@pointermove="onPointermove" @pointermove="onPointermove"
@pointerdown="onStartDraw" @pointerdown="onStartDraw"
@contextmenu="openContextMenu"
@dragover.prevent="onDragover" @dragover.prevent="onDragover"
@dragenter="onDragenter" @dragenter="onDragenter"
@dragleave="newArea = null" @dragleave="newArea = null"
@ -67,13 +80,15 @@
<script> <script>
import FieldArea from './area' import FieldArea from './area'
import ContextMenu from './context_menu'
export default { export default {
name: 'TemplatePage', name: 'TemplatePage',
components: { components: {
FieldArea FieldArea,
ContextMenu
}, },
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'assignDropAreaSize'], inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'assignDropAreaSize', 'selectedAreaRef'],
props: { props: {
image: { image: {
type: Object, type: Object,
@ -157,13 +172,14 @@ export default {
required: true required: true
} }
}, },
emits: ['draw', 'drop-field', 'remove-area', 'scroll-to'], emits: ['draw', 'drop-field', 'remove-area', 'copy-field', 'paste-field', 'scroll-to'],
data () { data () {
return { return {
areaRefs: [], areaRefs: [],
showMask: false, showMask: false,
resizeDirection: null, resizeDirection: null,
newArea: null newArea: null,
contextMenu: null
} }
}, },
computed: { computed: {
@ -211,6 +227,81 @@ export default {
this.image.metadata.width = e.target.naturalWidth this.image.metadata.width = e.target.naturalWidth
this.image.metadata.height = e.target.naturalHeight this.image.metadata.height = e.target.naturalHeight
}, },
openContextMenu (event) {
if (!this.editable) {
return
}
event.preventDefault()
event.stopPropagation()
const rect = this.$refs.image.getBoundingClientRect()
this.newArea = null
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()
this.newArea = null
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
}
},
closeContextMenu () {
this.contextMenu = null
this.newArea = null
this.showMask = false
},
handleCopy () {
if (this.contextMenu.area) {
this.selectedAreaRef.value = this.contextMenu.area
this.$emit('copy-field')
}
this.closeContextMenu()
},
handleDelete () {
if (this.contextMenu.area) {
this.$emit('remove-area', this.contextMenu.area)
}
this.closeContextMenu()
},
handlePaste () {
this.newArea = null
this.showMask = false
this.$emit('paste-field', {
page: this.number,
x: this.contextMenu.relativeX,
y: this.contextMenu.relativeY
})
this.closeContextMenu()
},
setAreaRefs (el) { setAreaRefs (el) {
if (el) { if (el) {
this.areaRefs.push(el) this.areaRefs.push(el)
@ -243,6 +334,10 @@ export default {
}) })
}, },
onStartDraw (e) { onStartDraw (e) {
if (e.button === 2) {
return
}
if (!this.allowDraw) { if (!this.allowDraw) {
return return
} }

Loading…
Cancel
Save