Manually upload signature (#521)
* Manually upload signature * Signature upload UI changes * fix signature on clear --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
parent
1ac71ecf8b
commit
5049ba7fb1
|
|
@ -216,6 +216,10 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
|
|
||||||
private function storeSignature(?string $value)
|
private function storeSignature(?string $value)
|
||||||
{
|
{
|
||||||
|
if ($value && preg_match('/^[\/\w\-. ]+$/', $value)) { // If it's filename
|
||||||
|
return $this->storeFile($value);
|
||||||
|
}
|
||||||
|
|
||||||
if ($value == null || !isset(explode(',', $value)[1])) {
|
if ($value == null || !isset(explode(',', $value)[1])) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,42 @@
|
||||||
<slot name="label" />
|
<slot name="label" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="loading || file"
|
||||||
|
:class="[
|
||||||
|
theme.SignatureInput.input,
|
||||||
|
theme.SignatureInput.spacing.horizontal,
|
||||||
|
theme.SignatureInput.spacing.vertical,
|
||||||
|
theme.SignatureInput.fontSize,
|
||||||
|
theme.SignatureInput.borderRadius,
|
||||||
|
{
|
||||||
|
'!ring-red-500 !ring-2 !border-transparent': hasError,
|
||||||
|
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
class="flex flex-wrap items-center justify-center gap-4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="loading"
|
||||||
|
class="text-gray-600 dark:text-gray-400"
|
||||||
|
>
|
||||||
|
<Loader class="mx-auto h-6 w-6" />
|
||||||
|
<p class="mt-2 text-center text-sm text-gray-500">
|
||||||
|
Uploading your file...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<uploaded-file
|
||||||
|
v-else
|
||||||
|
:key="file.url"
|
||||||
|
:file="file"
|
||||||
|
:theme="theme"
|
||||||
|
:show-remove="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<VueSignaturePad
|
<VueSignaturePad
|
||||||
|
v-else
|
||||||
ref="signaturePad"
|
ref="signaturePad"
|
||||||
:class="[
|
:class="[
|
||||||
theme.SignatureInput.input,
|
theme.SignatureInput.input,
|
||||||
|
|
@ -23,6 +58,26 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<template #bottom_after_help>
|
<template #bottom_after_help>
|
||||||
|
<small
|
||||||
|
v-if="!file"
|
||||||
|
:class="theme.default.help"
|
||||||
|
class="flex-auto"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref="actual-input"
|
||||||
|
class="hidden"
|
||||||
|
:multiple="false"
|
||||||
|
type="file"
|
||||||
|
accept=".pdf,.png,.jpg,.jpeg"
|
||||||
|
@change="manualFileUpload"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:class="theme.default.help"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="openFileUpload"
|
||||||
|
>Upload file instead</a>
|
||||||
|
</small>
|
||||||
|
|
||||||
<small :class="theme.default.help">
|
<small :class="theme.default.help">
|
||||||
<a
|
<a
|
||||||
:class="theme.default.help"
|
:class="theme.default.help"
|
||||||
|
|
@ -42,6 +97,7 @@
|
||||||
import { inputProps, useFormInput } from "./useFormInput.js"
|
import { inputProps, useFormInput } from "./useFormInput.js"
|
||||||
import InputWrapper from "./components/InputWrapper.vue"
|
import InputWrapper from "./components/InputWrapper.vue"
|
||||||
import { VueSignaturePad } from "vue-signature-pad"
|
import { VueSignaturePad } from "vue-signature-pad"
|
||||||
|
import { storeFile } from "~/lib/file-uploads.js"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SignatureInput",
|
name: "SignatureInput",
|
||||||
|
|
@ -57,12 +113,31 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
data: () => ({
|
||||||
|
file: null,
|
||||||
|
loading: false
|
||||||
|
}),
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
file: {
|
||||||
|
handler(file) {
|
||||||
|
this.compVal = file?.url || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
clear() {
|
clear() {
|
||||||
this.$refs.signaturePad.clearSignature()
|
this.file = null
|
||||||
|
this.$refs.signaturePad?.clearSignature()
|
||||||
this.onEnd()
|
this.onEnd()
|
||||||
},
|
},
|
||||||
onEnd() {
|
onEnd() {
|
||||||
|
if (!this.$refs.signaturePad) {
|
||||||
|
this.form[this.name] = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
this.$refs.signaturePad.clearSignature()
|
this.$refs.signaturePad.clearSignature()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -71,6 +146,39 @@ export default {
|
||||||
this.form[this.name] = !isEmpty && data ? data : null
|
this.form[this.name] = !isEmpty && data ? data : null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openFileUpload() {
|
||||||
|
if (this.disabled || !this.$refs['actual-input']) return
|
||||||
|
this.$refs['actual-input'].click()
|
||||||
|
},
|
||||||
|
manualFileUpload(e) {
|
||||||
|
const files = e.target.files
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
this.uploadFileToServer(files.item(i))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uploadFileToServer(file) {
|
||||||
|
if (this.disabled) return
|
||||||
|
this.loading = true
|
||||||
|
storeFile(file)
|
||||||
|
.then((response) => {
|
||||||
|
this.file = {
|
||||||
|
file: file,
|
||||||
|
url: file.name.split('.').slice(0, -1).join('.') + '_' + response.uuid + '.' + response.extension,
|
||||||
|
src: this.getFileSrc(file)
|
||||||
|
}
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.loading = false
|
||||||
|
this.file = null
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getFileSrc(file) {
|
||||||
|
if (file.type && file.type.split('/')[0] === 'image') {
|
||||||
|
return URL.createObjectURL(file)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
{{ file.file.name }}
|
{{ file.file.name }}
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
|
v-if="showRemove"
|
||||||
href="javascript:void(0);"
|
href="javascript:void(0);"
|
||||||
class="flex text-gray-400 rounded hover:bg-neutral-50 hover:text-red-500 dark:text-gray-600 p-1"
|
class="flex text-gray-400 rounded hover:bg-neutral-50 hover:text-red-500 dark:text-gray-600 p-1"
|
||||||
role="button"
|
role="button"
|
||||||
|
|
@ -70,6 +71,7 @@ export default {
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
file: { type: Object, default: null },
|
file: { type: Object, default: null },
|
||||||
|
showRemove: { type: Boolean, default: true },
|
||||||
theme: {
|
theme: {
|
||||||
type: Object, default: () => {
|
type: Object, default: () => {
|
||||||
const theme = inject("theme", null)
|
const theme = inject("theme", null)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue