Signature: add a signature zone manually

This commit is contained in:
nobohan 2024-09-04 10:14:08 +02:00 committed by Julien Fastré
parent 18af2ca70b
commit c23568032c
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
3 changed files with 128 additions and 43 deletions

View File

@ -83,7 +83,7 @@ export interface PDFPage {
height: number, height: number,
} }
export interface SignatureZone { export interface SignatureZone {
index: number, index: number | null,
x: number, x: number,
y: number, y: number,
width: number, width: number,
@ -98,3 +98,5 @@ export interface Signature {
} }
export type SignedState = 'pending' | 'signed' | 'rejected' | 'canceled' | 'error'; export type SignedState = 'pending' | 'signed' | 'rejected' | 'canceled' | 'error';
export type CanvasEvent = 'select' | 'add';

View File

@ -80,8 +80,32 @@
</div> </div>
<div class="col-12 p-4" id="action-buttons" v-if="signedState !== 'signed'"> <div class="col-12 p-4" id="action-buttons" v-if="signedState !== 'signed'">
<div class="row mb-3">
<div class="col-12 d-flex justify-content-end">
<div class="col-4 col-xl-3 gap-2 d-grid">
<button
v-if="adding"
class="btn btn-misc btn-cancel me-2 btn-sm"
@click="removeNewZone()"
>
{{ $t("remove_sign_zone") }}
</button>
</div>
<div class="col-4 gap-2 d-grid">
<button
class="btn btn-create btn-sm"
:class="{ active: canvasEvent === 'add' }"
@click="toggleAddZone()"
>
{{ $t("add_sign_zone") }}
</button>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-4">
<button <button
class="btn btn-action me-2" class="btn btn-action me-2"
:disabled="!userSignatureZone" :disabled="!userSignatureZone"
@ -90,7 +114,7 @@
{{ $t("sign") }} {{ $t("sign") }}
</button> </button>
</div> </div>
<div class="col-6 d-flex justify-content-end"> <div class="col-8 d-flex justify-content-end">
<button <button
class="btn btn-misc me-2" class="btn btn-misc me-2"
:hidden="!userSignatureZone" :hidden="!userSignatureZone"
@ -119,7 +143,12 @@
import { ref, Ref, reactive } from "vue"; import { ref, Ref, reactive } from "vue";
import { useToast } from "vue-toast-notification"; import { useToast } from "vue-toast-notification";
import "vue-toast-notification/dist/theme-sugar.css"; import "vue-toast-notification/dist/theme-sugar.css";
import { Signature, SignatureZone, SignedState } from "../../types"; import {
CanvasEvent,
Signature,
SignatureZone,
SignedState,
} from "../../types";
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import * as pdfjsLib from "pdfjs-dist"; import * as pdfjsLib from "pdfjs-dist";
import { import {
@ -143,11 +172,12 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.mjs";
const modalOpen: Ref<boolean> = ref(false); const modalOpen: Ref<boolean> = ref(false);
const loading: Ref<boolean> = ref(false); const loading: Ref<boolean> = ref(false);
const adding: Ref<boolean> = ref(false);
const canvasEvent: Ref<CanvasEvent> = ref("select");
const signedState: Ref<SignedState> = ref("pending"); const signedState: Ref<SignedState> = ref("pending");
const page: Ref<number> = ref(1); const page: Ref<number> = ref(1);
const pageCount: Ref<number> = ref(0); const pageCount: Ref<number> = ref(0);
let userSignatureZone: Ref<null | SignatureZone> = ref(null); let userSignatureZone: Ref<null | SignatureZone> = ref(null);
let pdfSource: Ref<string> = ref("");
let pdf = {} as PDFDocumentProxy; let pdf = {} as PDFDocumentProxy;
declare global { declare global {
@ -160,6 +190,8 @@ const $toast = useToast();
const signature = window.signature; const signature = window.signature;
console.log(signature);
const mountPdf = async (url: string) => { const mountPdf = async (url: string) => {
const loadingTask = pdfjsLib.getDocument(url); const loadingTask = pdfjsLib.getDocument(url);
pdf = await loadingTask.promise; pdf = await loadingTask.promise;
@ -202,33 +234,31 @@ async function downloadAndOpen(): Promise<Blob> {
const initPdf = () => { const initPdf = () => {
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement; const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement;
canvas.addEventListener( canvas.addEventListener("pointerup", canvasClick, false);
"pointerup",
(e: PointerEvent) => canvasClick(e, canvas),
false
);
setTimeout(() => addZones(page.value), 800); setTimeout(() => addZones(page.value), 800);
}; };
const scaleXToCanvas = (x: number, canvasWidth: number, PDFWidth: number) =>
Math.round((x * canvasWidth) / PDFWidth);
const scaleYToCanvas = (h: number, canvasHeight: number, PDFHeight: number) =>
Math.round((h * canvasHeight) / PDFHeight);
const hitSignature = ( const hitSignature = (
zone: SignatureZone, zone: SignatureZone,
xy: number[], xy: number[],
canvasWidth: number, canvasWidth: number,
canvasHeight: number canvasHeight: number
) => { ) =>
const scaleXToCanvas = (x: number) => scaleXToCanvas(zone.x, canvasWidth, zone.PDFPage.width) < xy[0] &&
Math.round((x * canvasWidth) / zone.PDFPage.width); xy[0] <
const scaleHeightToCanvas = (h: number) => scaleXToCanvas(zone.x + zone.width, canvasWidth, zone.PDFPage.width) &&
Math.round((h * canvasHeight) / zone.PDFPage.height); zone.PDFPage.height -
const scaleYToCanvas = (y: number) => scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height) <
Math.round(zone.PDFPage.height - scaleHeightToCanvas(y)); xy[1] &&
return ( xy[1] <
scaleXToCanvas(zone.x) < xy[0] && scaleYToCanvas(zone.height - zone.y, canvasHeight, zone.PDFPage.height) +
xy[0] < scaleXToCanvas(zone.x + zone.width) && zone.PDFPage.height;
scaleYToCanvas(zone.y) < xy[1] &&
xy[1] < scaleYToCanvas(zone.y) + scaleHeightToCanvas(zone.height)
);
};
const selectZone = (z: SignatureZone, canvas: HTMLCanvasElement) => { const selectZone = (z: SignatureZone, canvas: HTMLCanvasElement) => {
userSignatureZone.value = z; userSignatureZone.value = z;
@ -239,7 +269,7 @@ const selectZone = (z: SignatureZone, canvas: HTMLCanvasElement) => {
} }
}; };
const canvasClick = (e: PointerEvent, canvas: HTMLCanvasElement) => const selectZoneOnCanvas = (e: PointerEvent, canvas: HTMLCanvasElement) =>
signature.zones signature.zones
.filter((z) => z.PDFPage.index + 1 === page.value) .filter((z) => z.PDFPage.index + 1 === page.value)
.map((z) => { .map((z) => {
@ -256,6 +286,13 @@ const canvasClick = (e: PointerEvent, canvas: HTMLCanvasElement) =>
} }
}); });
const canvasClick = (e: PointerEvent) => {
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement;
canvasEvent.value === "select"
? selectZoneOnCanvas(e, canvas)
: addZoneOnCanvas(e, canvas);
};
const turnPage = async (upOrDown: number) => { const turnPage = async (upOrDown: number) => {
userSignatureZone.value = null; userSignatureZone.value = null;
page.value = page.value + upOrDown; page.value = page.value + upOrDown;
@ -290,12 +327,6 @@ const drawZone = (
) => { ) => {
const unselectedBlue = "#007bff"; const unselectedBlue = "#007bff";
const selectedBlue = "#034286"; const selectedBlue = "#034286";
const scaleXToCanvas = (x: number) =>
Math.round((x * canvasWidth) / zone.PDFPage.width);
const scaleHeightToCanvas = (h: number) =>
Math.round((h * canvasHeight) / zone.PDFPage.height);
const scaleYToCanvas = (y: number) =>
Math.round(zone.PDFPage.height - scaleHeightToCanvas(y));
ctx.strokeStyle = ctx.strokeStyle =
userSignatureZone.value?.index === zone.index userSignatureZone.value?.index === zone.index
? selectedBlue ? selectedBlue
@ -303,16 +334,22 @@ const drawZone = (
ctx.lineWidth = 2; ctx.lineWidth = 2;
ctx.lineJoin = "bevel"; ctx.lineJoin = "bevel";
ctx.strokeRect( ctx.strokeRect(
scaleXToCanvas(zone.x), scaleXToCanvas(zone.x, canvasWidth, zone.PDFPage.width),
scaleYToCanvas(zone.y), zone.PDFPage.height -
scaleXToCanvas(zone.width), scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height),
scaleHeightToCanvas(zone.height) scaleXToCanvas(zone.width, canvasWidth, zone.PDFPage.width),
scaleYToCanvas(zone.height, canvasHeight, zone.PDFPage.height)
); );
ctx.font = "bold 16px serif"; ctx.font = "bold 16px serif";
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillStyle = "black"; ctx.fillStyle = "black";
const xText = scaleXToCanvas(zone.x) + scaleXToCanvas(zone.width) / 2; const xText =
const yText = scaleYToCanvas(zone.y) + scaleHeightToCanvas(zone.height) / 2; scaleXToCanvas(zone.x, canvasWidth, zone.PDFPage.width) +
scaleXToCanvas(zone.width, canvasWidth, zone.PDFPage.width) / 2;
const yText =
zone.PDFPage.height -
scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height) +
scaleYToCanvas(zone.height, canvasHeight, zone.PDFPage.height) / 2;
if (userSignatureZone.value?.index === zone.index) { if (userSignatureZone.value?.index === zone.index) {
ctx.fillStyle = selectedBlue; ctx.fillStyle = selectedBlue;
ctx.fillText("Signer ici", xText, yText); ctx.fillText("Signer ici", xText, yText);
@ -414,14 +451,58 @@ const confirmSign = () => {
}; };
const undoSign = async () => { const undoSign = async () => {
// const canvas = document.querySelectorAll("canvas")[0]; signature.zones = signature.zones.filter((z) => z.index !== null);
// const ctx = canvas.getContext("2d");
// if (ctx && userSignatureZone.value) {
// //drawZone(userSignatureZone.value, ctx, canvas.width, canvas.height);
// }
await setPage(page.value); await setPage(page.value);
setTimeout(() => addZones(page.value), 200); setTimeout(() => addZones(page.value), 200);
userSignatureZone.value = null; userSignatureZone.value = null;
adding.value = false;
};
const toggleAddZone = () => {
canvasEvent.value === "select"
? (canvasEvent.value = "add")
: (canvasEvent.value = "select");
};
const addZoneOnCanvas = (e: PointerEvent, canvas: HTMLCanvasElement) => {
const BOX_WIDTH = 180;
const BOX_HEIGHT = 90;
const PDFPageHeight = canvas.height;
const PDFPageWidth = canvas.width;
const x = e.offsetX;
const y = e.offsetY;
const newZone: SignatureZone = {
index: null,
x:
scaleXToCanvas(x, canvas.width, PDFPageWidth) -
scaleXToCanvas(BOX_WIDTH / 2, canvas.width, PDFPageWidth),
y:
PDFPageHeight -
scaleYToCanvas(y, canvas.height, PDFPageHeight) +
scaleYToCanvas(BOX_HEIGHT / 2, canvas.height, PDFPageHeight),
width: BOX_WIDTH,
height: BOX_HEIGHT,
PDFPage: {
index: page.value - 1,
width: PDFPageWidth,
height: PDFPageHeight,
},
};
signature.zones.push(newZone);
setTimeout(() => addZones(page.value), 200);
canvasEvent.value = "select";
adding.value = true;
};
const removeNewZone = async () => {
signature.zones = signature.zones.filter((z) => z.index !== null);
userSignatureZone.value = null;
await setPage(page.value);
setTimeout(() => addZones(page.value), 200);
canvasEvent.value = "select";
adding.value = false;
}; };
downloadAndOpen(); downloadAndOpen();

View File

@ -16,7 +16,9 @@ const appMessages = {
last_sign_zone: 'Zone de signature précédente', last_sign_zone: 'Zone de signature précédente',
next_sign_zone: 'Zone de signature suivante', next_sign_zone: 'Zone de signature suivante',
electronic_signature_in_progress: 'Signature électronique en cours...', electronic_signature_in_progress: 'Signature électronique en cours...',
loading: 'Chargement...' loading: 'Chargement...',
add_sign_zone: 'Ajouter une zone de signature',
remove_sign_zone: 'Enlever la zone',
} }
} }