add field area controls
This commit is contained in:
@@ -7,54 +7,96 @@
|
||||
>
|
||||
<div
|
||||
v-if="field"
|
||||
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap hidden group-hover:block group-hover:z-10"
|
||||
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap group-hover:flex group-hover:z-10"
|
||||
:class="{ flex: isNameFocus || isSelected, hidden: !isNameFocus && !isSelected }"
|
||||
style="top: -25px; height: 25px"
|
||||
@mousedown.stop
|
||||
@pointerdown.stop
|
||||
>
|
||||
<span class="dropdown dropdown-start border-r">
|
||||
<label
|
||||
tabindex="0"
|
||||
title="Submitter"
|
||||
class="cursor-pointer text-base-100"
|
||||
@click="selectedAreaRef.value = area"
|
||||
>
|
||||
<button class="mx-1 w-3 h-3 rounded-full bg-yellow-600" />
|
||||
</label>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none"
|
||||
style="left: -1px"
|
||||
@click="closeDropdown"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm py-1 px-2"
|
||||
@click.prevent
|
||||
>
|
||||
Submitter 1
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<FieldType
|
||||
v-model="field.type"
|
||||
:button-width="27"
|
||||
:button-classes="'px-1'"
|
||||
:menu-classes="'bg-white rounded-t-none'"
|
||||
@click="selectedAreaRef.value = area"
|
||||
/>
|
||||
<span
|
||||
v-if="withName"
|
||||
ref="name"
|
||||
contenteditable
|
||||
class="pr-1 cursor-text outline-none block"
|
||||
style="min-width: 2px"
|
||||
@keydown.enter.prevent="onNameEnter"
|
||||
@focus="onNameFocus"
|
||||
@blur="onNameBlur"
|
||||
>{{ field.name || defaultName }}</span>
|
||||
<button
|
||||
v-for="(component, type, index) in iconComponents"
|
||||
:key="type"
|
||||
class="px-0.5 hover:text-base-100 hover:bg-base-content transition-colors"
|
||||
:class="{ 'bg-base-content text-base-100': field.type === type, 'rounded-tl': index === 0, 'rounded-tr': index === 4 }"
|
||||
@click="changeTypeTo(type)"
|
||||
class="pl-0.5 pr-1.5"
|
||||
@click.prevent="$emit('remove')"
|
||||
>
|
||||
<component
|
||||
:is="component"
|
||||
:width="20"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="bg-red-100 opacity-70 flex items-center justify-center h-full w-full"
|
||||
class="bg-red-100 opacity-50 flex items-center justify-center h-full w-full"
|
||||
>
|
||||
{{ field?.name || field?.type }}
|
||||
<span
|
||||
v-if="field"
|
||||
class="flex justify-center items-center space-x-1"
|
||||
>
|
||||
<component :is="fieldIcons[field.type]" />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="absolute top-0 bottom-0 right-0 left-0"
|
||||
/>
|
||||
<span
|
||||
class="h-2 w-2 right-0 bottom-0 bg-red-900 absolute cursor-nwse-resize"
|
||||
class="h-2 w-2 -right-1 rounded-full -bottom-1 bg-red-900 absolute cursor-nwse-resize"
|
||||
@mousedown.stop="startResize"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox } from '@tabler/icons-vue'
|
||||
import FieldType from './field_type'
|
||||
import Field from './field'
|
||||
|
||||
export default {
|
||||
name: 'FieldArea',
|
||||
components: {
|
||||
FieldType
|
||||
},
|
||||
inject: ['template', 'selectedAreaRef'],
|
||||
props: {
|
||||
bounds: {
|
||||
area: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0,
|
||||
h: 0
|
||||
}
|
||||
}
|
||||
required: true
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
@@ -62,25 +104,25 @@ export default {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag'],
|
||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove'],
|
||||
data () {
|
||||
return {
|
||||
isResize: false,
|
||||
isNameFocus: false,
|
||||
dragFrom: { x: 0, y: 0 }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconComponents () {
|
||||
return {
|
||||
text: IconTextSize,
|
||||
signature: IconWriting,
|
||||
date: IconCalendarEvent,
|
||||
image: IconPhoto,
|
||||
checkbox: IconCheckbox
|
||||
}
|
||||
defaultName: Field.computed.defaultName,
|
||||
fieldIcons: FieldType.computed.fieldIcons,
|
||||
withName () {
|
||||
return !['checkbox', 'radio'].includes(this.field.type) || this.field.options
|
||||
},
|
||||
isSelected () {
|
||||
return this.selectedAreaRef.value === this.area
|
||||
},
|
||||
positionStyle () {
|
||||
const { x, y, w, h } = this.bounds
|
||||
const { x, y, w, h } = this.area
|
||||
|
||||
return {
|
||||
top: y * 100 + '%',
|
||||
@@ -91,22 +133,47 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTypeTo (type) {
|
||||
this.field.type = type
|
||||
onNameFocus (e) {
|
||||
this.selectedAreaRef.value = this.area
|
||||
|
||||
this.isNameFocus = true
|
||||
this.$refs.name.style.minWidth = this.$refs.name.clientWidth + 'px'
|
||||
|
||||
if (!this.field.name) {
|
||||
setTimeout(() => {
|
||||
this.$refs.name.innerText = ' '
|
||||
}, 1)
|
||||
}
|
||||
},
|
||||
onNameBlur (e) {
|
||||
this.isNameFocus = false
|
||||
this.$refs.name.style.minWidth = ''
|
||||
|
||||
if (e.target.innerText.trim()) {
|
||||
this.field.name = e.target.innerText.trim()
|
||||
} else {
|
||||
this.field.name = ''
|
||||
this.$refs.name.innerText = this.defaultName
|
||||
}
|
||||
},
|
||||
onNameEnter (e) {
|
||||
this.$refs.name.blur()
|
||||
},
|
||||
resize (e) {
|
||||
if (e.toElement.id === 'mask') {
|
||||
this.bounds.w = e.layerX / e.toElement.clientWidth - this.bounds.x
|
||||
this.bounds.h = e.layerY / e.toElement.clientHeight - this.bounds.y
|
||||
this.area.w = e.layerX / e.toElement.clientWidth - this.area.x
|
||||
this.area.h = e.layerY / e.toElement.clientHeight - this.area.y
|
||||
}
|
||||
},
|
||||
drag (e) {
|
||||
if (e.toElement.id === 'mask') {
|
||||
this.bounds.x = (e.layerX - this.dragFrom.x) / e.toElement.clientWidth
|
||||
this.bounds.y = (e.layerY - this.dragFrom.y) / e.toElement.clientHeight
|
||||
this.area.x = (e.layerX - this.dragFrom.x) / e.toElement.clientWidth
|
||||
this.area.y = (e.layerY - this.dragFrom.y) / e.toElement.clientHeight
|
||||
}
|
||||
},
|
||||
startDrag (e) {
|
||||
this.selectedAreaRef.value = this.area
|
||||
|
||||
const rect = e.target.getBoundingClientRect()
|
||||
|
||||
this.dragFrom = { x: e.clientX - rect.left, y: e.clientY - rect.top }
|
||||
@@ -123,6 +190,8 @@ export default {
|
||||
this.$emit('stop-drag')
|
||||
},
|
||||
startResize () {
|
||||
this.selectedAreaRef.value = this.area
|
||||
|
||||
document.addEventListener('mousemove', this.resize)
|
||||
document.addEventListener('mouseup', this.stopResize)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user