Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 8s
* refactor: enable profile signature option in ProfileMenu * feat: add signature api function * refactor: add new translation keys for 'Draw' and 'New Upload' in English and Thai * refactor: update image URL variable and improve translation keys in CanvasComponent and MainLayout * refactor: get function * feat: add delete signature function * feat: add canvas manipulation functions and integrate signature submission in MainLayout (unfinished) * chore(deps): update --------- Co-authored-by: puriphatt <puriphat@frappet.com> Co-authored-by: Methapon2001 <61303214+Methapon2001@users.noreply.github.com>
232 lines
5.5 KiB
Vue
232 lines
5.5 KiB
Vue
<script lang="ts" setup>
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
import { useQuasar } from 'quasar';
|
|
import SignaturePad from 'signature_pad';
|
|
import Cropper from 'cropperjs';
|
|
|
|
defineExpose({ setCanvas, getCanvas, clearCanvas, clearUpload });
|
|
|
|
const $q = useQuasar();
|
|
const isDarkActive = computed(() => $q.dark.isActive);
|
|
|
|
const canvasRef = ref<HTMLCanvasElement>();
|
|
const signaturePad = ref();
|
|
const currentColor = ref('blue');
|
|
|
|
const imageRef = ref<HTMLImageElement>();
|
|
const cropper = ref();
|
|
|
|
const tab = ref('draw');
|
|
const uploadFile = ref<File | undefined>(undefined);
|
|
const imgUrl = 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') imgUrl.value = reader.result;
|
|
});
|
|
|
|
element.addEventListener('change', () => {
|
|
uploadFile.value = element.files?.[0];
|
|
if (uploadFile.value) {
|
|
reader.readAsDataURL(uploadFile.value);
|
|
}
|
|
});
|
|
|
|
return element;
|
|
})();
|
|
|
|
async function initializeSignaturePad() {
|
|
const canvas = canvasRef.value;
|
|
|
|
if (canvas) {
|
|
signaturePad.value = new SignaturePad(canvas, {
|
|
penColor: 'blue',
|
|
});
|
|
} else {
|
|
console.warn('Canvas reference not found. SignaturePad not initialized.');
|
|
}
|
|
}
|
|
|
|
async function initializeCropper(image?: HTMLImageElement) {
|
|
console.log(image);
|
|
if (image) {
|
|
cropper.value = new Cropper(image, {
|
|
aspectRatio: 16 / 9,
|
|
crop(event) {
|
|
console.log(event.detail.x);
|
|
console.log(event.detail.y);
|
|
console.log(event.detail.width);
|
|
console.log(event.detail.height);
|
|
console.log(event.detail.rotate);
|
|
console.log(event.detail.scaleX);
|
|
console.log(event.detail.scaleY);
|
|
},
|
|
});
|
|
} else {
|
|
console.warn('Canvas reference not found. Cropper not initialized.');
|
|
}
|
|
}
|
|
|
|
function changeColor(color: string) {
|
|
signaturePad.value.penColor = color;
|
|
currentColor.value = color;
|
|
}
|
|
|
|
function setCanvas() {
|
|
const data = signaturePad.value.toDataURL('image/png');
|
|
return data;
|
|
}
|
|
|
|
function getCanvas(signature: string) {
|
|
signaturePad.value.fromDataURL(signature);
|
|
}
|
|
|
|
function clearCanvas() {
|
|
signaturePad.value.clear();
|
|
}
|
|
|
|
function clearUpload() {
|
|
imgUrl.value = '';
|
|
}
|
|
|
|
watch(
|
|
() => tab.value,
|
|
async () => {
|
|
await initializeSignaturePad();
|
|
},
|
|
);
|
|
|
|
onMounted(async () => {
|
|
await initializeSignaturePad();
|
|
});
|
|
</script>
|
|
<template>
|
|
<div class="surface-1 column full-width full-height">
|
|
<q-tabs
|
|
v-model="tab"
|
|
dense
|
|
align="left"
|
|
class="text-grey surface-2"
|
|
active-color="primary"
|
|
indicator-color="primary"
|
|
>
|
|
<div class="row justify-between full-width items-center">
|
|
<div class="row">
|
|
<q-tab
|
|
name="draw"
|
|
:label="$t('general.draw')"
|
|
style="border-top-left-radius: var(--radius-2)"
|
|
/>
|
|
<q-tab name="upload" :label="$t('general.upload')" />
|
|
</div>
|
|
|
|
<div class="q-pr-md">
|
|
<q-btn
|
|
v-if="tab === 'upload'"
|
|
dense
|
|
flat
|
|
:label="$t('general.newUpload')"
|
|
color="info"
|
|
@click="inputFile.click()"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</q-tabs>
|
|
<q-separator />
|
|
|
|
<section v-show="tab === 'draw'" class="q-pa-md col">
|
|
<div class="column relative-position">
|
|
<article
|
|
class="absolute-top-right q-ma-md q-gutter-x-md row items-center"
|
|
>
|
|
<span
|
|
v-for="color in ['black', 'red', 'blue']"
|
|
:key="color"
|
|
:class="{ active: currentColor === color }"
|
|
class="dot"
|
|
:style="`background-color: ${color}`"
|
|
@click="changeColor(color)"
|
|
>
|
|
<q-icon
|
|
v-if="currentColor === color"
|
|
name="mdi-check"
|
|
color="white"
|
|
size="sm"
|
|
/>
|
|
</span>
|
|
</article>
|
|
|
|
<canvas
|
|
class="signature-canvas"
|
|
ref="canvasRef"
|
|
id="signature-pad"
|
|
width="766"
|
|
height="364"
|
|
></canvas>
|
|
</div>
|
|
</section>
|
|
|
|
<section v-show="tab === 'upload'" class="q-pa-md col">
|
|
<div
|
|
class="bordered upload-border rounded column items-center justify-center full-height"
|
|
>
|
|
<q-img
|
|
v-show="imgUrl"
|
|
ref="imageRef"
|
|
:src="imgUrl ?? ''"
|
|
style="object-fit: cover; width: 100%; height: 100%"
|
|
/>
|
|
<div v-if="!imgUrl">
|
|
<q-icon
|
|
name="mdi-cloud-upload"
|
|
size="10rem"
|
|
style="color: hsla(var(--text-mute) / 0.2)"
|
|
/>
|
|
<div class="text-center">
|
|
<q-btn
|
|
unelevated
|
|
color="info"
|
|
:label="$t('general.upload')"
|
|
icon="mdi-plus"
|
|
@click="inputFile.click()"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
<style scoped lang="scss">
|
|
.signature-canvas {
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--radius-2);
|
|
}
|
|
|
|
.dot {
|
|
height: 25px;
|
|
width: 25px;
|
|
border-radius: 50%;
|
|
display: inline-flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
&.active {
|
|
height: 35px;
|
|
width: 35px;
|
|
}
|
|
}
|
|
|
|
.color-palette {
|
|
position: absolute;
|
|
display: inline-block;
|
|
}
|
|
|
|
.upload-border {
|
|
border-style: dashed;
|
|
border-color: hsl(var(--info-bg));
|
|
}
|
|
</style>
|