feat: signature upload tab & i18n

This commit is contained in:
puriphatt 2024-06-27 03:00:22 +00:00
parent e5859474be
commit 4809a081f6
3 changed files with 158 additions and 74 deletions

View file

@ -1,24 +1,53 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import SignaturePad from 'signature_pad'; import SignaturePad from 'signature_pad';
import { useQuasar } from 'quasar';
defineExpose({ clearCanvas }); defineExpose({ clearCanvas, clearUpload });
let canvasRef: HTMLCanvasElement | null = null; const $q = useQuasar();
const canvasRef = ref<HTMLCanvasElement>();
const signaturePad = ref(); const signaturePad = ref();
const currentColor = ref('blue'); const currentColor = ref('blue');
const tab = ref('draw'); const tab = ref('draw');
const initializeSignaturePad = () => { const isDarkActive = computed(() => $q.dark.isActive);
if (canvasRef) {
signaturePad.value = new SignaturePad(canvasRef, { const uploadFile = ref<File | undefined>(undefined);
backgroundColor: 'rgb(248,249,250)', const profileUrl = ref<string | null>('');
const inputFile = (() => {
const element = document.createElement('input');
element.type = 'file';
element.accept = 'image/*';
const reader = new FileReader();
reader.addEventListener('load', () => {
if (typeof reader.result === 'string') profileUrl.value = reader.result;
});
element.addEventListener('change', () => {
uploadFile.value = element.files?.[0];
if (uploadFile.value) {
reader.readAsDataURL(uploadFile.value);
}
});
return element;
})();
function initializeSignaturePad(canva?: HTMLCanvasElement) {
if (canva) {
signaturePad.value = new SignaturePad(canva, {
backgroundColor: isDarkActive.value
? 'rgb(21,25,29)'
: 'rgb(248,249,250)',
penColor: 'blue', penColor: 'blue',
}); });
} else { } else {
console.warn('Canvas reference not found. SignaturePad not initialized.'); console.warn('Canvas reference not found. SignaturePad not initialized.');
} }
}; }
function changeColor(color: string) { function changeColor(color: string) {
signaturePad.value.penColor = color; signaturePad.value.penColor = color;
@ -29,9 +58,19 @@ function clearCanvas() {
signaturePad.value.clear(); signaturePad.value.clear();
} }
function clearUpload() {
profileUrl.value = '';
}
watch(
() => tab.value,
async () => {
initializeSignaturePad(canvasRef.value);
},
);
onMounted(() => { onMounted(() => {
canvasRef = document.querySelector('canvas'); initializeSignaturePad(canvasRef.value);
initializeSignaturePad();
}); });
</script> </script>
<template> <template>
@ -44,76 +83,112 @@ onMounted(() => {
active-color="primary" active-color="primary"
indicator-color="primary" indicator-color="primary"
> >
<q-tab <div class="row justify-between full-width items-center">
name="draw" <div class="row">
label="Draw" <q-tab
style="border-top-left-radius: var(--radius-2)" name="draw"
/> label="Draw"
<q-tab disable name="upload" label="Upload" /> style="border-top-left-radius: var(--radius-2)"
/>
<q-tab name="upload" label="Upload" />
</div>
<div class="q-pr-md">
<q-btn
dense
flat
v-if="tab === 'upload'"
:label="$t('newUpload')"
color="info"
@click="inputFile.click()"
/>
</div>
</div>
</q-tabs> </q-tabs>
<q-separator /> <q-separator />
<q-tab-panels v-model="tab" animated> <div v-show="tab === 'draw'" class="q-pa-md">
<q-tab-panel name="draw"> <div class="column relative-position">
<div class="column relative-position"> <div class="absolute-top-right q-ma-md q-gutter-x-md row items-center">
<div <span
class="absolute-top-right q-ma-md q-gutter-x-md row items-center" class="dot"
:class="{ active: currentColor === 'black' }"
style="background-color: black"
@click="changeColor('black')"
> >
<span <q-icon
class="dot" v-if="currentColor === 'black'"
:class="{ active: currentColor === 'black' }" name="mdi-check"
style="background-color: black" color="white"
@click="changeColor('black')" size="sm"
> />
<q-icon </span>
v-if="currentColor === 'black'" <span
name="mdi-check" :class="{ active: currentColor === 'red' }"
color="white" class="dot"
size="sm" style="background-color: red"
/> @click="changeColor('red')"
</span> >
<span <q-icon
:class="{ active: currentColor === 'red' }" v-if="currentColor === 'red'"
class="dot" name="mdi-check"
style="background-color: red" color="white"
@click="changeColor('red')" size="sm"
> />
<q-icon </span>
v-if="currentColor === 'red'" <span
name="mdi-check" :class="{ active: currentColor === 'blue' }"
color="white" class="dot"
size="sm" style="background-color: blue"
/> @click="changeColor('blue')"
</span> >
<span <q-icon
:class="{ active: currentColor === 'blue' }" v-if="currentColor === 'blue'"
class="dot" name="mdi-check"
style="background-color: blue" color="white"
@click="changeColor('blue')" size="sm"
> />
<q-icon </span>
v-if="currentColor === 'blue'"
name="mdi-check"
color="white"
size="sm"
/>
</span>
</div>
<canvas
class="signature-canvas"
ref="canvasRef"
id="signature-pad"
width="700"
height="300"
></canvas>
</div> </div>
</q-tab-panel>
</q-tab-panels>
<!-- <div class="text-right q-gutter-x-md"> <canvas
<q-btn id="clear" label="Clear" @click="signaturePad.clear()" /> class="signature-canvas"
</div> --> ref="canvasRef"
id="signature-pad"
width="700"
height="310"
></canvas>
</div>
</div>
<div v-if="tab === 'upload'" class="q-pa-md">
<div
class="bordered upload-border rounded column items-center justify-center"
style="height: 312px"
>
<q-img
v-if="profileUrl"
:src="profileUrl"
style="object-fit: cover; width: 100%; height: 100%"
/>
<div v-else>
<q-icon
name="mdi-cloud-upload"
size="10rem"
style="color: hsla(var(--text-mute) / 0.2)"
/>
<div>
<q-btn
unelevated
color="info"
:label="$t('uploadFile')"
icon="mdi-plus"
@click="inputFile.click()"
/>
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
@ -140,4 +215,9 @@ onMounted(() => {
position: absolute; position: absolute;
display: inline-block; display: inline-block;
} }
.upload-border {
border-style: dashed;
border-color: hsl(var(--info-bg));
}
</style> </style>

View file

@ -53,6 +53,8 @@ export default {
theme: 'Theme', theme: 'Theme',
light: 'Light', light: 'Light',
dark: 'Dark', dark: 'Dark',
uploadFile: 'Upload File',
newUpload: 'New Upload',
baseOnDevice: 'Base on Device', baseOnDevice: 'Base on Device',
...status, ...status,
...main, ...main,

View file

@ -53,6 +53,8 @@ export default {
theme: 'ธีม', theme: 'ธีม',
light: 'สว่าง', light: 'สว่าง',
dark: 'มืด', dark: 'มืด',
uploadFile: 'อัปโหลดไฟล์',
newUpload: 'อัปโหลดใหม่',
baseOnDevice: 'สีตามอุปกรณ์', baseOnDevice: 'สีตามอุปกรณ์',
...status, ...status,
...main, ...main,