style submitter form
This commit is contained in:
+11
-14
@@ -31,20 +31,8 @@ button[disabled] .enabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button .disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button[disabled] .disabled {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
button .enabled {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
button[disabled] .enabled {
|
||||
display: none;
|
||||
select:required:invalid {
|
||||
@apply text-gray-300;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@@ -54,6 +42,15 @@ button[disabled] .enabled {
|
||||
.base-input {
|
||||
@apply input input-bordered bg-white;
|
||||
}
|
||||
|
||||
.base-button {
|
||||
@apply btn btn-neutral text-white text-base;
|
||||
}
|
||||
|
||||
.base-checkbox {
|
||||
@apply checkbox rounded bg-white checkbox-sm;
|
||||
}
|
||||
|
||||
.base-radio {
|
||||
@apply radio bg-white radio-sm;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex cursor-pointer bg-red-100 bg-opacity-60 absolute"
|
||||
class="flex cursor-pointer bg-red-100 absolute border"
|
||||
:style="computedStyle"
|
||||
:class="{ 'border-red-100 bg-opacity-70': !isActive, 'border-red-500 border-dashed bg-opacity-30 z-10': isActive }"
|
||||
>
|
||||
<div
|
||||
v-if="!isActive && !modelValue"
|
||||
class="absolute top-0 bottom-0 right-0 left-0 items-center justify-center h-full w-full"
|
||||
>
|
||||
<span
|
||||
v-if="field"
|
||||
class="flex justify-center items-center h-full opacity-50"
|
||||
>
|
||||
<component
|
||||
:is="fieldIcons[field.type]"
|
||||
width="100%"
|
||||
height="100%"
|
||||
class="max-h-10 text-base-content"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="isActive"
|
||||
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap"
|
||||
>
|
||||
{{ field.name || fieldNames[field.type] }}
|
||||
</div>
|
||||
<div
|
||||
v-if="isActive"
|
||||
ref="scrollToElem"
|
||||
class="absolute -top-20"
|
||||
/>
|
||||
<img
|
||||
v-if="field.type === 'image' && image"
|
||||
class="object-contain"
|
||||
@@ -23,12 +51,14 @@
|
||||
</a>
|
||||
</div>
|
||||
<span v-else>
|
||||
{{ value }}
|
||||
{{ modelValue }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FieldArea',
|
||||
props: {
|
||||
@@ -36,11 +66,16 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: [Array, String, Number, Object],
|
||||
modelValue: {
|
||||
type: [Array, String, Number, Object, Boolean],
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
attachmentsIndex: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -51,24 +86,49 @@ export default {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['update:model-value'],
|
||||
computed: {
|
||||
fieldNames () {
|
||||
return {
|
||||
text: 'Text',
|
||||
signature: 'Signature',
|
||||
date: 'Date',
|
||||
image: 'Image',
|
||||
file: 'File',
|
||||
select: 'Select',
|
||||
checkbox: 'Checkbox',
|
||||
radio: 'Radio'
|
||||
}
|
||||
},
|
||||
fieldIcons () {
|
||||
return {
|
||||
text: IconTextSize,
|
||||
signature: IconWriting,
|
||||
date: IconCalendarEvent,
|
||||
image: IconPhoto,
|
||||
file: IconPaperclip,
|
||||
select: IconSelect,
|
||||
checkbox: IconCheckbox,
|
||||
radio: IconCircleDot
|
||||
}
|
||||
},
|
||||
image () {
|
||||
if (this.field.type === 'image') {
|
||||
return this.attachmentsIndex[this.value]
|
||||
return this.attachmentsIndex[this.modelValue]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
signature () {
|
||||
if (this.field.type === 'signature') {
|
||||
return this.attachmentsIndex[this.value]
|
||||
return this.attachmentsIndex[this.modelValue]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
attachments () {
|
||||
if (this.field.type === 'attachment') {
|
||||
return (this.value || []).map((uuid) => this.attachmentsIndex[uuid])
|
||||
return (this.modelValue || []).map((uuid) => this.attachmentsIndex[uuid])
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@
|
||||
<Teleport :to="`#page-${area.attachment_uuid}-${area.page}`">
|
||||
<FieldArea
|
||||
:ref="setAreaRef"
|
||||
v-model="values[field.uuid]"
|
||||
:field="field"
|
||||
:area="area"
|
||||
:is-active="currentField === field"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:value="values[field.uuid]"
|
||||
@click="$emit('focus-field', field)"
|
||||
/>
|
||||
</Teleport>
|
||||
@@ -44,6 +45,11 @@ export default {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
currentField: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['focus-field'],
|
||||
@@ -59,7 +65,7 @@ export default {
|
||||
scrollIntoField (field) {
|
||||
this.areaRefs.find((area) => {
|
||||
if (area.field === field) {
|
||||
area.$el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
area.$refs.scrollToElem.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
|
||||
return true
|
||||
} else {
|
||||
|
||||
@@ -1,29 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="modelValue.length">
|
||||
<div v-if="modelValue.length">
|
||||
<div
|
||||
v-for="(val, index) in modelValue"
|
||||
:key="index"
|
||||
class="flex mb-2"
|
||||
>
|
||||
<a
|
||||
v-if="val"
|
||||
:href="attachmentsIndex[val].url"
|
||||
>
|
||||
{{ attachmentsIndex[val].filename }}
|
||||
</a>
|
||||
<input
|
||||
:value="val"
|
||||
type="hidden"
|
||||
:name="`values[${field.uuid}][]`"
|
||||
>
|
||||
<a
|
||||
v-if="val"
|
||||
class="flex items-center space-x-1.5 w-full"
|
||||
:href="attachmentsIndex[val].url"
|
||||
target="_blank"
|
||||
>
|
||||
<IconPaperclip
|
||||
:width="16"
|
||||
class="flex-none"
|
||||
:heigh="16"
|
||||
/>
|
||||
<span>
|
||||
{{ attachmentsIndex[val].filename }}
|
||||
</span>
|
||||
</a>
|
||||
<button
|
||||
v-if="modelValue"
|
||||
@click.prevent="removeAttachment(val)"
|
||||
>
|
||||
Remove
|
||||
<IconTrashX
|
||||
:width="18"
|
||||
:heigh="19"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<template v-else>
|
||||
<input
|
||||
value=""
|
||||
@@ -32,7 +45,7 @@
|
||||
>
|
||||
</template>
|
||||
<FileDropzone
|
||||
:message="'Attachments'"
|
||||
:message="`Upload ${field.name || 'Attachments'}`"
|
||||
:submitter-slug="submitterSlug"
|
||||
@upload="onUpload"
|
||||
/>
|
||||
@@ -41,11 +54,14 @@
|
||||
|
||||
<script>
|
||||
import FileDropzone from './dropzone'
|
||||
import { IconPaperclip, IconTrashX } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'AttachmentStep',
|
||||
components: {
|
||||
FileDropzone
|
||||
FileDropzone,
|
||||
IconPaperclip,
|
||||
IconTrashX
|
||||
},
|
||||
props: {
|
||||
field: {
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
<template>
|
||||
<div
|
||||
v-for="(option, index) in field.options"
|
||||
:key="index"
|
||||
>
|
||||
<label :for="field.uuid + option">
|
||||
<input
|
||||
:id="field.uuid + option"
|
||||
:ref="setInputRef"
|
||||
type="checkbox"
|
||||
:name="`values[${field.uuid}][]`"
|
||||
:value="option"
|
||||
:checked="modelValue.includes(option)"
|
||||
@change="onChange"
|
||||
<label
|
||||
v-if="field.name"
|
||||
:for="field.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ field.name }}</label>
|
||||
<div class="space-y-3.5">
|
||||
<div
|
||||
v-for="(option, index) in field.options"
|
||||
:key="index"
|
||||
>
|
||||
<label
|
||||
:for="field.uuid + option"
|
||||
class="flex items-center space-x-3"
|
||||
>
|
||||
{{ option }}
|
||||
</label>
|
||||
<input
|
||||
:id="field.uuid + option"
|
||||
:ref="setInputRef"
|
||||
type="checkbox"
|
||||
:name="`values[${field.uuid}][]`"
|
||||
:value="option"
|
||||
class="base-checkbox !h-7 !w-7"
|
||||
:checked="modelValue.includes(option)"
|
||||
@change="onChange"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{{ option }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SheckboxStep',
|
||||
|
||||
@@ -1,33 +1,52 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex h-20 w-full"
|
||||
class="flex h-32 w-full"
|
||||
@dragover.prevent
|
||||
@drop.prevent="onDropFiles"
|
||||
>
|
||||
<label
|
||||
:for="inputId"
|
||||
class="w-full"
|
||||
class="w-full relative bg-base-300 hover:bg-base-200 rounded-md border border-base-content border-dashed"
|
||||
>
|
||||
Upload
|
||||
{{ message }}
|
||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<IconCloudUpload
|
||||
:width="30"
|
||||
:height="30"
|
||||
/>
|
||||
<div
|
||||
v-if="message"
|
||||
class="font-medium mb-1"
|
||||
>
|
||||
{{ message }}
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
<span class="font-medium">Click to upload</span> or drag and drop
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
:id="inputId"
|
||||
ref="input"
|
||||
:multiple="multiple"
|
||||
:accept="accept"
|
||||
type="file"
|
||||
class="hidden"
|
||||
@change="onSelectFiles"
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
:id="inputId"
|
||||
ref="input"
|
||||
:multiple="multiple"
|
||||
:accept="accept"
|
||||
type="file"
|
||||
class="hidden"
|
||||
@change="onSelectFiles"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DirectUpload } from '@rails/activestorage'
|
||||
import { IconCloudUpload } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FileDropzone',
|
||||
components: {
|
||||
IconCloudUpload
|
||||
},
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
@@ -62,7 +81,9 @@ export default {
|
||||
e.preventDefault()
|
||||
|
||||
this.uploadFiles(this.$refs.input.files).then(() => {
|
||||
this.$refs.input.value = ''
|
||||
if (this.$refs.input) {
|
||||
this.$refs.input.value = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
async uploadFiles (files) {
|
||||
|
||||
@@ -4,20 +4,15 @@
|
||||
:fields="submitterFields"
|
||||
:values="values"
|
||||
:attachments-index="attachmentsIndex"
|
||||
@focus-field="goToField"
|
||||
:current-field="currentField"
|
||||
@focus-field="goToField($event, false, true)"
|
||||
/>
|
||||
<button
|
||||
v-if="currentStep !== 0"
|
||||
@click="goToField(submitterFields[currentStep - 1], true)"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
{{ currentField.type }}
|
||||
<form
|
||||
v-if="!isCompleted"
|
||||
ref="form"
|
||||
:action="submitPath"
|
||||
method="post"
|
||||
class="md:mx-16"
|
||||
@submit.prevent="submitStep"
|
||||
>
|
||||
<input
|
||||
@@ -36,46 +31,59 @@
|
||||
name="_method"
|
||||
type="hidden"
|
||||
>
|
||||
<div>
|
||||
<template v-if="currentField.type === 'text'">
|
||||
<label :for="currentField.uuid">{{ currentField.name || 'Text' }}</label>
|
||||
<div class="mt-4">
|
||||
<div v-if="currentField.type === 'text'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div>
|
||||
<input
|
||||
:id="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
autofocus
|
||||
class="text-xl"
|
||||
class="base-input !text-2xl w-full"
|
||||
:required="currentField.required"
|
||||
placeholder="Type here..."
|
||||
type="text"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="currentField.type === 'date'">
|
||||
<label :for="currentField.uuid">{{ currentField.name || 'Date' }}</label>
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'date'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div>
|
||||
<input
|
||||
:id="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
class="text-xl"
|
||||
class="base-input !text-2xl w-full text-center"
|
||||
autofocus
|
||||
:required="currentField.required"
|
||||
type="date"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="currentField.type === 'select'">
|
||||
<label :for="currentField.uuid">{{ currentField.name || 'Date' }}</label>
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'select'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<select
|
||||
:id="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
:required="currentField.required"
|
||||
:required="true"
|
||||
class="select base-input !text-2xl w-full text-center font-normal"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
@change="values[currentField.uuid] = $event.target.value"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
disabled
|
||||
:selected="!values[currentField.uuid]"
|
||||
>
|
||||
Select your option
|
||||
@@ -83,35 +91,72 @@
|
||||
<option
|
||||
v-for="(option, index) in currentField.options"
|
||||
:key="index"
|
||||
:select="values[currentField.uuid] == option"
|
||||
:selected="values[currentField.uuid] == option"
|
||||
:value="option"
|
||||
>
|
||||
{{ option }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
<template v-else-if="currentField.type === 'radio'">
|
||||
<div
|
||||
v-for="(option, index) in currentField.options"
|
||||
:key="index"
|
||||
>
|
||||
<label :for="currentField.uuid + option">
|
||||
<input
|
||||
:id="currentField.uuid + option"
|
||||
v-model="values[currentField.uuid]"
|
||||
type="radio"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
:value="option"
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'radio' && currentField.options?.length">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div class="space-y-3.5">
|
||||
<div
|
||||
v-for="(option, index) in currentField.options"
|
||||
:key="index"
|
||||
>
|
||||
<label
|
||||
:for="currentField.uuid + option"
|
||||
class="flex items-center space-x-3"
|
||||
>
|
||||
{{ option }}
|
||||
</label>
|
||||
<input
|
||||
:id="currentField.uuid + option"
|
||||
v-model="values[currentField.uuid]"
|
||||
type="radio"
|
||||
class="base-radio !h-7 !w-7"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
:value="option"
|
||||
required
|
||||
>
|
||||
<span class="text-xl">
|
||||
{{ option }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<CheckboxStep
|
||||
v-else-if="currentField.type === 'checkbox'"
|
||||
v-else-if="currentField.type === 'checkbox' && currentField.options?.length"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
/>
|
||||
<div v-else-if="['radio', 'checkbox'].includes(currentField.type)">
|
||||
<div class="flex justify-center">
|
||||
<label
|
||||
:for="currentField.uuid"
|
||||
class="flex items-center space-x-3"
|
||||
>
|
||||
<input
|
||||
:id="currentField.uuid"
|
||||
:model-value="values[currentField.uuid]"
|
||||
:type="currentField.type"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
:value="true"
|
||||
class="!h-7 !w-7"
|
||||
:class="{'base-radio' : currentField.type === 'radio', 'base-checkbox': currentField.type === 'checkbox'}"
|
||||
:checked="!!values[currentField.uuid]"
|
||||
@click="values[currentField.uuid] = !values[currentField.uuid]"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{{ currentField.name || currentField.type }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<ImageStep
|
||||
v-else-if="currentField.type === 'image'"
|
||||
v-model="values[currentField.uuid]"
|
||||
@@ -138,8 +183,11 @@
|
||||
@attached="attachments.push($event)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">
|
||||
<div class="mt-8">
|
||||
<button
|
||||
type="submit"
|
||||
class="base-button w-full"
|
||||
>
|
||||
<span v-if="isSubmitting">
|
||||
Submitting...
|
||||
</span>
|
||||
@@ -148,6 +196,18 @@
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="flex items-center mt-5 mb-1">
|
||||
<a
|
||||
v-for="(field, index) in submitterFields"
|
||||
:key="field.uuid"
|
||||
href="#"
|
||||
class="inline border border-base-300 h-3 w-3 rounded-full mx-1"
|
||||
:class="{ 'bg-base-200': index === currentStep, 'bg-base-content': index < currentStep, 'bg-white': index > currentStep }"
|
||||
@click.prevent="goToField(field, true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<FormCompleted
|
||||
v-else
|
||||
@@ -234,7 +294,7 @@ export default {
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
goToField (field, scrollToArea = false) {
|
||||
goToField (field, scrollToArea = false, clickUpload = false) {
|
||||
this.currentStep = this.submitterFields.indexOf(field)
|
||||
|
||||
this.$nextTick(() => {
|
||||
@@ -243,6 +303,10 @@ export default {
|
||||
}
|
||||
|
||||
this.$refs.form.querySelector('input[type="date"], input[type="text"], select')?.focus()
|
||||
|
||||
if (clickUpload && !this.values[this.currentField.uuid]) {
|
||||
this.$refs.form.querySelector('input[type="file"]')?.click()
|
||||
}
|
||||
})
|
||||
},
|
||||
async submitStep () {
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<img
|
||||
v-if="modelValue"
|
||||
class="w-80"
|
||||
:src="attachmentsIndex[modelValue].url"
|
||||
>
|
||||
<button
|
||||
v-if="modelValue"
|
||||
@click.prevent="remove"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
<div v-if="modelValue">
|
||||
<div class="flex justify-between items-center w-full mb-2">
|
||||
<label
|
||||
class="label text-2xl"
|
||||
>{{ field.name || 'Image' }}</label>
|
||||
<button
|
||||
class="btn btn-outline btn-sm"
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
Reupload
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<img
|
||||
:src="attachmentsIndex[modelValue].url"
|
||||
class="h-52 border border-base-300 rounded mx-auto"
|
||||
>
|
||||
</div>
|
||||
<input
|
||||
:value="modelValue"
|
||||
type="hidden"
|
||||
:name="`values[${field.uuid}]`"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<FileDropzone
|
||||
:message="'Image'"
|
||||
v-if="!modelValue"
|
||||
:message="`Upload ${field.name || 'Image'}`"
|
||||
:submitter-slug="submitterSlug"
|
||||
:accept="'image/*'"
|
||||
@upload="onImageUpload"
|
||||
@@ -27,10 +37,13 @@
|
||||
|
||||
<script>
|
||||
import FileDropzone from './dropzone'
|
||||
import { IconReload } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'ImageStep',
|
||||
components: {
|
||||
FileDropzone
|
||||
FileDropzone,
|
||||
IconReload
|
||||
},
|
||||
props: {
|
||||
field: {
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex justify-between items-center w-full mb-2">
|
||||
<label
|
||||
class="label text-2xl"
|
||||
>{{ field.name || 'Signature' }}</label>
|
||||
<button
|
||||
v-if="modelValue"
|
||||
class="btn btn-outline btn-sm"
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
Redraw
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-outline btn-sm"
|
||||
@click.prevent="clear"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
:value="modelValue"
|
||||
type="hidden"
|
||||
@@ -8,32 +29,26 @@
|
||||
<img
|
||||
v-if="modelValue"
|
||||
:src="attachmentsIndex[modelValue].url"
|
||||
class="w-full bg-white border border-base-300 rounded"
|
||||
>
|
||||
<canvas
|
||||
v-show="!modelValue"
|
||||
ref="canvas"
|
||||
class="bg-white border border-base-300 rounded"
|
||||
/>
|
||||
<button
|
||||
v-if="modelValue"
|
||||
@click.prevent="remove"
|
||||
>
|
||||
Redraw
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
@click.prevent="clear"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SignaturePad from 'signature_pad'
|
||||
import { DirectUpload } from '@rails/activestorage'
|
||||
import { IconReload } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'SignatureStep',
|
||||
components: {
|
||||
IconReload
|
||||
},
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
@@ -56,6 +71,9 @@ export default {
|
||||
},
|
||||
emits: ['attached', 'update:model-value'],
|
||||
mounted () {
|
||||
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth
|
||||
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth / 3
|
||||
|
||||
this.pad = new SignaturePad(this.$refs.canvas)
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -291,6 +291,10 @@ export default {
|
||||
required: true
|
||||
}
|
||||
|
||||
if (this.dragFieldType === 'select') {
|
||||
field.options = ['']
|
||||
}
|
||||
|
||||
const fieldArea = {
|
||||
x: (area.x - 6) / area.maskW,
|
||||
y: area.y / area.maskH,
|
||||
|
||||
Reference in New Issue
Block a user