add cell field type
This commit is contained in:
@@ -17,7 +17,7 @@ module Api
|
|||||||
schema: [%i[attachment_uuid name]],
|
schema: [%i[attachment_uuid name]],
|
||||||
submitters: [%i[name uuid]],
|
submitters: [%i[name uuid]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type, :required,
|
fields: [[:uuid, :submitter_uuid, :name, :type, :required,
|
||||||
{ options: [], areas: [%i[x y w h attachment_uuid page]] }]])
|
{ options: [], areas: [%i[x y w h cell_w attachment_uuid page]] }]])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ window.customElements.define('template-builder', class extends HTMLElement {
|
|||||||
template: reactive(JSON.parse(this.dataset.template))
|
template: reactive(JSON.parse(this.dataset.template))
|
||||||
})
|
})
|
||||||
|
|
||||||
this.app.config.globalProperties.$t = (key) => TemplateBuilder.i18n[key] || key
|
|
||||||
|
|
||||||
this.app.mount(this.appElem)
|
this.app.mount(this.appElem)
|
||||||
|
|
||||||
this.appendChild(this.appElem)
|
this.appendChild(this.appElem)
|
||||||
|
|||||||
@@ -82,6 +82,19 @@
|
|||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="field.type === 'cells'"
|
||||||
|
class="w-full flex items-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(char, index) in modelValue"
|
||||||
|
:key="index"
|
||||||
|
class="text-center flex-none"
|
||||||
|
:style="{ width: (area.cell_w / area.w * 100) + '%' }"
|
||||||
|
>
|
||||||
|
{{ char }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="flex items-center px-0.5"
|
class="flex items-center px-0.5"
|
||||||
@@ -100,7 +113,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck } from '@tabler/icons-vue'
|
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3 } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldArea',
|
name: 'FieldArea',
|
||||||
@@ -153,6 +166,7 @@ export default {
|
|||||||
fieldNames () {
|
fieldNames () {
|
||||||
return {
|
return {
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
|
cells: 'Text',
|
||||||
signature: 'Signature',
|
signature: 'Signature',
|
||||||
date: 'Date',
|
date: 'Date',
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
@@ -173,6 +187,7 @@ export default {
|
|||||||
select: IconSelect,
|
select: IconSelect,
|
||||||
checkbox: IconCheckbox,
|
checkbox: IconCheckbox,
|
||||||
radio: IconCircleDot,
|
radio: IconCircleDot,
|
||||||
|
cells: IconColumns3,
|
||||||
multiple: IconChecks
|
multiple: IconChecks
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
type="hidden"
|
type="hidden"
|
||||||
>
|
>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div v-if="currentField.type === 'text'">
|
<div v-if="['cells', 'text'].includes(currentField.type)">
|
||||||
<label
|
<label
|
||||||
v-if="currentField.name"
|
v-if="currentField.name"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
@@ -369,10 +369,14 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
saveStep () {
|
saveStep () {
|
||||||
return fetch(this.submitPath, {
|
if (this.isCompleted) {
|
||||||
method: 'POST',
|
return Promise.resolve({})
|
||||||
body: new FormData(this.$refs.form)
|
} else {
|
||||||
})
|
return fetch(this.submitPath, {
|
||||||
|
method: 'POST',
|
||||||
|
body: new FormData(this.$refs.form)
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async submitStep () {
|
async submitStep () {
|
||||||
this.isSubmitting = true
|
this.isSubmitting = true
|
||||||
|
|||||||
@@ -6,10 +6,29 @@
|
|||||||
@mousedown.stop="startDrag"
|
@mousedown.stop="startDrag"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isSelected || !field?.type"
|
v-if="isSelected || isDraw"
|
||||||
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="borderColors[submitterIndex]"
|
:class="borderColors[submitterIndex]"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="field.type === 'cells' && (isSelected || isDraw)"
|
||||||
|
class="top-0 bottom-0 right-0 left-0 absolute"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(cellW, index) in cells"
|
||||||
|
:key="index"
|
||||||
|
class="absolute top-0 bottom-0 border-r"
|
||||||
|
:class="borderColors[submitterIndex]"
|
||||||
|
:style="{ left: (cellW / area.w * 100) + '%' }"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="index === 0"
|
||||||
|
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"
|
||||||
|
@mousedown.stop="startResizeCell"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field?.type"
|
v-if="field?.type"
|
||||||
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap group-hover:flex group-hover:z-10"
|
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap group-hover:flex group-hover:z-10"
|
||||||
@@ -118,6 +137,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
isDraw: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -135,7 +159,21 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
defaultName: Field.computed.defaultName,
|
defaultName: Field.computed.defaultName,
|
||||||
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldIcons: FieldType.computed.fieldIcons,
|
fieldIcons: FieldType.computed.fieldIcons,
|
||||||
|
cells () {
|
||||||
|
const cells = []
|
||||||
|
|
||||||
|
let currentWidth = 0
|
||||||
|
|
||||||
|
while (currentWidth + (this.area.cell_w + this.area.cell_w / 4) < this.area.w) {
|
||||||
|
currentWidth += this.area.cell_w || 9999999
|
||||||
|
|
||||||
|
cells.push(currentWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cells
|
||||||
|
},
|
||||||
submitter () {
|
submitter () {
|
||||||
return this.template.submitters.find((s) => s.uuid === this.field.submitter_uuid)
|
return this.template.submitters.find((s) => s.uuid === this.field.submitter_uuid)
|
||||||
},
|
},
|
||||||
@@ -187,6 +225,29 @@ export default {
|
|||||||
}, 1)
|
}, 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
startResizeCell (e) {
|
||||||
|
document.addEventListener('mousemove', this.onResizeCell)
|
||||||
|
document.addEventListener('mouseup', this.stopResizeCell)
|
||||||
|
|
||||||
|
this.$emit('start-resize', 'ew')
|
||||||
|
},
|
||||||
|
stopResizeCell (e) {
|
||||||
|
document.removeEventListener('mousemove', this.onResizeCell)
|
||||||
|
document.removeEventListener('mouseup', this.stopResizeCell)
|
||||||
|
|
||||||
|
this.$emit('stop-resize')
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
|
onResizeCell (e) {
|
||||||
|
if (e.toElement.id === 'mask') {
|
||||||
|
const positionX = e.layerX / (e.toElement.clientWidth - 1)
|
||||||
|
|
||||||
|
if (positionX > this.area.x) {
|
||||||
|
this.area.cell_w = positionX - this.area.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
maybeUpdateOptions () {
|
maybeUpdateOptions () {
|
||||||
if (!['radio', 'multiple', 'select'].includes(this.field.type)) {
|
if (!['radio', 'multiple', 'select'].includes(this.field.type)) {
|
||||||
delete this.field.options
|
delete this.field.options
|
||||||
@@ -195,6 +256,14 @@ export default {
|
|||||||
if (['select', 'multiple', 'radio'].includes(this.field.type)) {
|
if (['select', 'multiple', 'radio'].includes(this.field.type)) {
|
||||||
this.field.options ||= ['']
|
this.field.options ||= ['']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(this.field.areas || []).forEach((area) => {
|
||||||
|
if (this.field.type === 'cells') {
|
||||||
|
area.cell_w = area.w * 2 / Math.floor(area.w / area.h)
|
||||||
|
} else {
|
||||||
|
delete area.cell_w
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onNameBlur (e) {
|
onNameBlur (e) {
|
||||||
this.isNameFocus = false
|
this.isNameFocus = false
|
||||||
@@ -256,7 +325,7 @@ export default {
|
|||||||
document.addEventListener('mousemove', this.resize)
|
document.addEventListener('mousemove', this.resize)
|
||||||
document.addEventListener('mouseup', this.stopResize)
|
document.addEventListener('mouseup', this.stopResize)
|
||||||
|
|
||||||
this.$emit('start-resize')
|
this.$emit('start-resize', 'nwse')
|
||||||
},
|
},
|
||||||
stopResize () {
|
stopResize () {
|
||||||
document.removeEventListener('mousemove', this.resize)
|
document.removeEventListener('mousemove', this.resize)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
:document="document"
|
:document="document"
|
||||||
:is-drag="!!dragFieldType"
|
:is-drag="!!dragFieldType"
|
||||||
:is-draw="!!drawField"
|
:draw-field="drawField"
|
||||||
@draw="onDraw"
|
@draw="onDraw"
|
||||||
@drop-field="onDropfield"
|
@drop-field="onDropfield"
|
||||||
@remove-area="removeArea"
|
@remove-area="removeArea"
|
||||||
@@ -141,12 +141,10 @@ import Contenteditable from './contenteditable'
|
|||||||
import DocumentPreview from './preview'
|
import DocumentPreview from './preview'
|
||||||
import { IconUsersPlus, IconDeviceFloppy, IconInnerShadowTop } from '@tabler/icons-vue'
|
import { IconUsersPlus, IconDeviceFloppy, IconInnerShadowTop } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import i18n from './i18n'
|
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplateBuilder',
|
name: 'TemplateBuilder',
|
||||||
i18n,
|
|
||||||
components: {
|
components: {
|
||||||
Upload,
|
Upload,
|
||||||
Document,
|
Document,
|
||||||
@@ -352,6 +350,10 @@ export default {
|
|||||||
fieldArea.h = baseArea.h
|
fieldArea.h = baseArea.h
|
||||||
fieldArea.y = fieldArea.y - baseArea.h / 2
|
fieldArea.y = fieldArea.y - baseArea.h / 2
|
||||||
|
|
||||||
|
if (this.dragFieldType === 'cells') {
|
||||||
|
fieldArea.cell_w = baseArea.cell_w || (baseArea.w / 5)
|
||||||
|
}
|
||||||
|
|
||||||
field.areas = [fieldArea]
|
field.areas = [fieldArea]
|
||||||
|
|
||||||
this.selectedAreaRef.value = fieldArea
|
this.selectedAreaRef.value = fieldArea
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
:number="index"
|
:number="index"
|
||||||
:areas="areasIndex[index]"
|
:areas="areasIndex[index]"
|
||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
:is-draw="isDraw"
|
:draw-field="drawField"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
:image="image"
|
:image="image"
|
||||||
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
||||||
@@ -38,10 +38,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
isDraw: {
|
drawField: {
|
||||||
type: Boolean,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: null
|
||||||
},
|
},
|
||||||
isDrag: {
|
isDrag: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|||||||
@@ -206,12 +206,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
defaultName () {
|
defaultName () {
|
||||||
const typeIndex = this.template.fields.filter((f) => f.type === this.field.type).indexOf(this.field)
|
const typeIndex = this.template.fields.filter((f) => f.type === this.field.type).indexOf(this.field)
|
||||||
|
|
||||||
const suffix = { multiple: 'Select', radio: 'Group' }[this.field.type] || 'Field'
|
const suffix = { multiple: 'Select', radio: 'Group' }[this.field.type] || 'Field'
|
||||||
|
|
||||||
return `${this.$t(this.field.type)} ${suffix} ${typeIndex + 1}`
|
return `${this.fieldNames[this.field.type]} ${suffix} ${typeIndex + 1}`
|
||||||
},
|
},
|
||||||
areas () {
|
areas () {
|
||||||
return this.field.areas || []
|
return this.field.areas || []
|
||||||
@@ -241,6 +242,14 @@ export default {
|
|||||||
if (['radio', 'multiple', 'select'].includes(this.field.type)) {
|
if (['radio', 'multiple', 'select'].includes(this.field.type)) {
|
||||||
this.field.options ||= ['']
|
this.field.options ||= ['']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(this.field.areas || []).forEach((area) => {
|
||||||
|
if (this.field.type === 'cells') {
|
||||||
|
area.cell_w = area.w * 2 / Math.floor(area.w / area.h)
|
||||||
|
} else {
|
||||||
|
delete area.cell_w
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onNameBlur (e) {
|
onNameBlur (e) {
|
||||||
if (e.target.innerText.trim()) {
|
if (e.target.innerText.trim()) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<span class="dropdown">
|
<span class="dropdown">
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
:title="$t(modelValue)"
|
:title="fieldNames[modelValue]"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
:width="20"
|
:width="20"
|
||||||
/>
|
/>
|
||||||
{{ $t(type) }}
|
{{ fieldNames[type] }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks } from '@tabler/icons-vue'
|
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3 } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FiledTypeDropdown',
|
name: 'FiledTypeDropdown',
|
||||||
@@ -68,6 +68,20 @@ export default {
|
|||||||
},
|
},
|
||||||
emits: ['update:model-value'],
|
emits: ['update:model-value'],
|
||||||
computed: {
|
computed: {
|
||||||
|
fieldNames () {
|
||||||
|
return {
|
||||||
|
text: 'Text',
|
||||||
|
signature: 'Signature',
|
||||||
|
date: 'Date',
|
||||||
|
image: 'Image',
|
||||||
|
file: 'File',
|
||||||
|
select: 'Select',
|
||||||
|
checkbox: 'Checkbox',
|
||||||
|
multiple: 'Multiple',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Cells'
|
||||||
|
}
|
||||||
|
},
|
||||||
fieldIcons () {
|
fieldIcons () {
|
||||||
return {
|
return {
|
||||||
text: IconTextSize,
|
text: IconTextSize,
|
||||||
@@ -77,6 +91,7 @@ export default {
|
|||||||
file: IconPaperclip,
|
file: IconPaperclip,
|
||||||
select: IconSelect,
|
select: IconSelect,
|
||||||
checkbox: IconCheckbox,
|
checkbox: IconCheckbox,
|
||||||
|
cells: IconColumns3,
|
||||||
multiple: IconChecks,
|
multiple: IconChecks,
|
||||||
radio: IconCircleDot
|
radio: IconCircleDot
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<div class="flex items-center flex-col px-2 py-2">
|
<div class="flex items-center flex-col px-2 py-2">
|
||||||
<component :is="icon" />
|
<component :is="icon" />
|
||||||
<span class="text-xs mt-1">
|
<span class="text-xs mt-1">
|
||||||
{{ $t(type) }}
|
{{ fieldNames[type] }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -98,6 +98,7 @@ export default {
|
|||||||
},
|
},
|
||||||
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
||||||
computed: {
|
computed: {
|
||||||
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldIcons: FieldType.computed.fieldIcons,
|
fieldIcons: FieldType.computed.fieldIcons,
|
||||||
submitterFields () {
|
submitterFields () {
|
||||||
return this.fields.filter((f) => f.submitter_uuid === this.selectedSubmitter.uuid)
|
return this.fields.filter((f) => f.submitter_uuid === this.selectedSubmitter.uuid)
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
export default {
|
|
||||||
text: 'Text',
|
|
||||||
signature: 'Signature',
|
|
||||||
date: 'Date',
|
|
||||||
image: 'Image',
|
|
||||||
file: 'File',
|
|
||||||
select: 'Select',
|
|
||||||
checkbox: 'Checkbox',
|
|
||||||
multiple: 'Multiple',
|
|
||||||
radio: 'Radio'
|
|
||||||
}
|
|
||||||
@@ -18,24 +18,25 @@
|
|||||||
:ref="setAreaRefs"
|
:ref="setAreaRefs"
|
||||||
:area="item.area"
|
:area="item.area"
|
||||||
:field="item.field"
|
:field="item.field"
|
||||||
@start-resize="[showMask = true, isResize = true]"
|
@start-resize="resizeDirection = $event"
|
||||||
@stop-resize="[showMask = false, isResize = false]"
|
@stop-resize="resizeDirection = null"
|
||||||
@start-drag="[showMask = true, isMove = true]"
|
@start-drag="isMove = true"
|
||||||
@stop-drag="[showMask = false, isMove = false]"
|
@stop-drag="isMove = false"
|
||||||
@remove="$emit('remove-area', item.area)"
|
@remove="$emit('remove-area', item.area)"
|
||||||
/>
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
v-if="newArea"
|
v-if="newArea"
|
||||||
:field="{ submitter_uuid: selectedSubmitter.uuid }"
|
:is-draw="true"
|
||||||
|
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || 'text' }"
|
||||||
:area="newArea"
|
:area="newArea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="isDrag || showMask"
|
v-show="resizeDirection || isMove || isDrag || showMask"
|
||||||
id="mask"
|
id="mask"
|
||||||
ref="mask"
|
ref="mask"
|
||||||
class="top-0 bottom-0 left-0 right-0 absolute z-10"
|
class="top-0 bottom-0 left-0 right-0 absolute z-10"
|
||||||
:class="{ 'cursor-grab': isDrag || isMove, 'cursor-nwse-resize': isResize || isDraw }"
|
:class="{ 'cursor-grab': isDrag || isMove, 'cursor-nwse-resize': drawField, [resizeDirectionClasses[resizeDirection]]: !!resizeDirectionClasses }"
|
||||||
@pointermove="onPointermove"
|
@pointermove="onPointermove"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
@@ -66,10 +67,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
isDraw: {
|
drawField: {
|
||||||
type: Boolean,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: null
|
||||||
},
|
},
|
||||||
isDrag: {
|
isDrag: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -87,11 +88,17 @@ export default {
|
|||||||
areaRefs: [],
|
areaRefs: [],
|
||||||
showMask: false,
|
showMask: false,
|
||||||
isMove: false,
|
isMove: false,
|
||||||
isResize: false,
|
resizeDirection: null,
|
||||||
newArea: null
|
newArea: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
resizeDirectionClasses () {
|
||||||
|
return {
|
||||||
|
nwse: 'cursor-nwse-resize',
|
||||||
|
ew: 'cursor-ew-resize'
|
||||||
|
}
|
||||||
|
},
|
||||||
width () {
|
width () {
|
||||||
return this.image.metadata.width
|
return this.image.metadata.width
|
||||||
},
|
},
|
||||||
@@ -148,19 +155,29 @@ export default {
|
|||||||
this.newArea.y = e.layerY / this.$refs.mask.clientHeight
|
this.newArea.y = e.layerY / this.$refs.mask.clientHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.drawField?.type === 'cells') {
|
||||||
|
this.newArea.cell_w = this.newArea.h * (this.$refs.mask.clientHeight / this.$refs.mask.clientWidth)
|
||||||
|
}
|
||||||
|
|
||||||
this.newArea.w = Math.abs(dx)
|
this.newArea.w = Math.abs(dx)
|
||||||
this.newArea.h = Math.abs(dy)
|
this.newArea.h = Math.abs(dy)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPointerup (e) {
|
onPointerup (e) {
|
||||||
if (this.newArea) {
|
if (this.newArea) {
|
||||||
this.$emit('draw', {
|
const area = {
|
||||||
x: this.newArea.x,
|
x: this.newArea.x,
|
||||||
y: this.newArea.y,
|
y: this.newArea.y,
|
||||||
w: this.newArea.w,
|
w: this.newArea.w,
|
||||||
h: this.newArea.h,
|
h: this.newArea.h,
|
||||||
page: this.number
|
page: this.number
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if ('cell_w' in this.newArea) {
|
||||||
|
area.cell_w = this.newArea.cell_w
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('draw', area)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showMask = false
|
this.showMask = false
|
||||||
|
|||||||
@@ -14,6 +14,15 @@
|
|||||||
<div class="w-full p-[0.2vw] flex items-center justify-center">
|
<div class="w-full p-[0.2vw] flex items-center justify-center">
|
||||||
<%= svg_icon('check', class: "aspect-square #{area['w'] > area['h'] ? '!w-auto !h-full' : '!w-full !h-auto'}") %>
|
<%= svg_icon('check', class: "aspect-square #{area['w'] > area['h'] ? '!w-auto !h-full' : '!w-full !h-auto'}") %>
|
||||||
</div>
|
</div>
|
||||||
|
<% elsif field['type'] == 'cells' %>
|
||||||
|
<% cell_width = area['cell_w'] / area['w'] * 100 %>
|
||||||
|
<div class="w-full flex items-center">
|
||||||
|
<% (0..(area['w'] / area['cell_w']).ceil).each do |index| %>
|
||||||
|
<% if value[index] %>
|
||||||
|
<div class="text-center flex-none" style="width: <%= cell_width %>%;"><%= value[index] %></div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<% elsif field['type'] == 'date' %>
|
<% elsif field['type'] == 'date' %>
|
||||||
<div class="flex items-center px-0.5">
|
<div class="flex items-center px-0.5">
|
||||||
<%= l(Date.parse(value)) %>
|
<%= l(Date.parse(value)) %>
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ module Submissions
|
|||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(submitter)
|
def call(submitter)
|
||||||
layouter = HexaPDF::Layout::TextLayouter.new(valign: :center)
|
layouter = HexaPDF::Layout::TextLayouter.new(valign: :center)
|
||||||
|
cell_layouter = HexaPDF::Layout::TextLayouter.new(valign: :center, align: :center)
|
||||||
|
|
||||||
template = submitter.submission.template
|
template = submitter.submission.template
|
||||||
|
|
||||||
cert = submitter.submission.template.account.encrypted_configs
|
cert = submitter.submission.template.account.encrypted_configs
|
||||||
@@ -129,6 +131,17 @@ module Submissions
|
|||||||
width: PdfIcons::WIDTH * scale,
|
width: PdfIcons::WIDTH * scale,
|
||||||
height: PdfIcons::HEIGHT * scale
|
height: PdfIcons::HEIGHT * scale
|
||||||
)
|
)
|
||||||
|
when 'cells'
|
||||||
|
cell_width = area['cell_w'] * width
|
||||||
|
|
||||||
|
value.chars.each_with_index do |char, index|
|
||||||
|
text = HexaPDF::Layout::TextFragment.create(char, font: pdf.fonts.add(FONT_NAME),
|
||||||
|
font_size: FONT_SIZE)
|
||||||
|
|
||||||
|
cell_layouter.fit([text], cell_width, area['h'] * height)
|
||||||
|
.draw(canvas, ((area['x'] * width) + (cell_width * index)),
|
||||||
|
height - (area['y'] * height))
|
||||||
|
end
|
||||||
else
|
else
|
||||||
value = I18n.l(Date.parse(value)) if field['type'] == 'date'
|
value = I18n.l(Date.parse(value)) if field['type'] == 'date'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user