Merge branch '318-signature-vue-app-show-full-doc' into 'master'

Resolve "[App de signature] Pouvoir voir le document en continu (toutes les pages ensemble)"

Closes #318

See merge request Chill-Projet/chill-bundles!761
This commit is contained in:
Julien Fastré 2024-12-05 16:31:04 +00:00
commit f5c1b5cf8a
4 changed files with 168 additions and 52 deletions

View File

@ -0,0 +1,5 @@
kind: Feature
body: Show all the pages of the documents in the signature app
time: 2024-12-05T17:23:41.866322287+01:00
custom:
Issue: "318"

View File

@ -0,0 +1,11 @@
chill_main:
workflow_signature:
base_signer:
document_kinds:
- { key: id_card, labels: [ { lang: fr, label: "Carte d'identité" } ] }
- { key: passport, labels: [ { lang: fr, label: "Passeport" } ] }
- { key: drivers_license, labels: [ { lang: fr, label: "Permis de conduire" } ] }
- { key: visa_long_stay, labels: [ { lang: fr, label: "Visa de long séjour" } ] }
- { key: resident_permit, labels: [ { lang: fr, label: "Carte de séjour" } ] }
- { key: residency_card, labels: [ { lang: fr, label: "Carte de résident" } ] }
- { key: provisionary_residency_permit, labels: [ { lang: fr, label: "Autorisation provisoire de séjour" } ] }

View File

@ -26,9 +26,9 @@
</template>
</modal>
</teleport>
<div class="col-12 m-auto">
<div class="col-12 m-auto sticky-top">
<div class="row justify-content-center border-bottom pdf-tools d-md-none">
<div class="col text-center turn-page">
<div class="col-5 text-center turn-page">
<select
class="form-select form-select-sm"
id="zoomSelect"
@ -57,9 +57,35 @@
</button>
</template>
<template v-if="pageCount > 1">
<button
class="btn btn-light btn-xs p-1"
:disabled="page <= 1"
@click="turnPage(-1)"
>
</button>
<span>{{ page }}/{{ pageCount }}</span>
<button
class="btn btn-light btn-xs p-1"
:disabled="page >= pageCount"
@click="turnPage(1)"
>
</button>
<input
type="checkbox"
id="checkboxMulti"
v-model="multiPage"
@change="toggleMultiPage"
/>
<label class="form-check-label" for="checkboxMulti">
{{ $t("all_pages") }}
</label>
</template>
</div>
<div
v-if="signature.zones.length > 1"
v-if="signature.zones.length > 0"
class="col-5 p-0 text-center turnSignature"
>
<button
@ -120,7 +146,7 @@
<div
class="row justify-content-center border-bottom pdf-tools d-none d-md-flex"
>
<div class="col-3 text-center turn-page ps-3">
<div class="col-5 text-center turn-page ps-3">
<select
class="form-select form-select-sm"
id="zoomSelect"
@ -148,10 +174,19 @@
>
</button>
<input
type="checkbox"
id="checkboxMulti"
v-model="multiPage"
@change="toggleMultiPage"
/>
<label class="form-check-label" for="checkboxMulti">
{{ $t("see_all_pages") }}
</label>
</template>
</div>
<div
v-if="signature.zones.length > 1 && signedState !== 'signed'"
v-if="signature.zones.length > 0 && signedState !== 'signed'"
class="col-4 d-xl-none text-center turnSignature p-0"
>
<button
@ -171,7 +206,7 @@
</button>
</div>
<div
v-if="signature.zones.length > 1 && signedState !== 'signed'"
v-if="signature.zones.length > 0 && signedState !== 'signed'"
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
>
<button
@ -233,12 +268,19 @@
</div>
</div>
<div
v-if="multiPage"
class="col-xs-12 col-md-12 col-lg-9 m-auto my-5 text-center d-flex flex-column"
:class="{ onAddZone: canvasEvent === 'add' }"
>
<canvas v-for="p in pageCount" :key="p" :id="`canvas-${p}`"></canvas>
</div>
<div
v-else
class="col-xs-12 col-md-12 col-lg-9 m-auto my-5 text-center"
:class="{ onAddZone: canvasEvent === 'add' }"
>
<canvas class="m-auto" id="canvas"></canvas>
</div>
<div class="col-xs-12 col-md-12 col-lg-9 m-auto p-4" id="action-buttons">
<div class="row">
<div class="col d-flex">
@ -298,6 +340,7 @@ import {download_and_decrypt_doc, download_doc_as_pdf} from "../StoredObjectButt
pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.mjs";
const multiPage: Ref<boolean> = ref(true);
const modalOpen: Ref<boolean> = ref(false);
const loading: Ref<boolean> = ref(false);
const adding: Ref<boolean> = ref(false);
@ -364,23 +407,37 @@ const $toast = useToast();
const signature = window.signature;
const setZoomLevel = (zoomLevel: string) => {
const setZoomLevel = async (zoomLevel: string) => {
zoom.value = Number.parseFloat(zoomLevel);
setPage(page.value);
setTimeout(() => drawAllZones(page.value), 200);
await resetPages();
setTimeout(drawAllZones, 200);
};
const mountPdf = async (doc: ArrayBuffer) => {
const loadingTask = pdfjsLib.getDocument(doc);
pdf = await loadingTask.promise;
pageCount.value = pdf.numPages;
await setPage(page.value);
if (multiPage.value) {
await setAllPages();
} else {
await setPage(1);
}
};
const getCanvas = (page: number) =>
multiPage.value
? (document.getElementById(`canvas-${page}`) as HTMLCanvasElement)
: (document.querySelectorAll("canvas")[0] as HTMLCanvasElement);
const getCanvasId = (canvas: HTMLCanvasElement) => {
const number = canvas.id.split("-").pop();
return number ? parseInt(number) : 0;
};
const getRenderContext = (pdfPage: PDFPageProxy) => {
const scale = 1 * zoom.value;
const viewport = pdfPage.getViewport({ scale });
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement;
const canvas = getCanvas(pdfPage.pageNumber);
const context = canvas.getContext("2d") as CanvasRenderingContext2D;
canvas.height = viewport.height;
canvas.width = viewport.width;
@ -391,6 +448,9 @@ const getRenderContext = (pdfPage: PDFPageProxy) => {
};
};
const setAllPages = async () =>
Array.from(Array(pageCount.value).keys()).map((p) => setPage(p + 1));
const setPage = async (page: number) => {
const pdfPage = await pdf.getPage(page);
const renderContext = getRenderContext(pdfPage);
@ -412,10 +472,34 @@ async function downloadAndOpen(): Promise<Blob> {
return raw;
}
const addCanvasEvents = () => {
if (multiPage.value) {
Array.from(Array(pageCount.value).keys()).map((p) => {
const canvas = getCanvas(p + 1);
canvas.addEventListener(
"pointerup",
(e) => canvasClick(e, canvas),
false
);
});
} else {
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement;
canvas.addEventListener("pointerup", (e) => canvasClick(e, canvas), false);
}
};
const initPdf = () => {
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement;
canvas.addEventListener("pointerup", canvasClick, false);
setTimeout(() => drawAllZones(page.value), 800);
addCanvasEvents();
setTimeout(drawAllZones, 800);
};
const resetPages = () =>
multiPage.value ? setAllPages() : setPage(page.value);
const toggleMultiPage = async () => {
await resetPages();
setTimeout(drawAllZones, 200);
addCanvasEvents();
};
const scaleXToCanvas = (x: number, canvasWidth: number, PDFWidth: number) =>
@ -427,35 +511,36 @@ const scaleYToCanvas = (h: number, canvasHeight: number, PDFHeight: number) =>
const hitSignature = (
zone: SignatureZone,
xy: number[],
canvasWidth: number,
canvasHeight: number
canvas: HTMLCanvasElement
) =>
scaleXToCanvas(zone.x, canvasWidth, zone.PDFPage.width) < xy[0] &&
scaleXToCanvas(zone.x, canvas.width, zone.PDFPage.width) < xy[0] &&
xy[0] <
scaleXToCanvas(zone.x + zone.width, canvasWidth, zone.PDFPage.width) &&
scaleXToCanvas(zone.x + zone.width, canvas.width, zone.PDFPage.width) &&
zone.PDFPage.height * zoom.value -
scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height) <
scaleYToCanvas(zone.y, canvas.height, zone.PDFPage.height) <
xy[1] &&
xy[1] <
scaleYToCanvas(zone.height - zone.y, canvasHeight, zone.PDFPage.height) +
scaleYToCanvas(zone.height - zone.y, canvas.height, zone.PDFPage.height) +
zone.PDFPage.height * zoom.value;
const selectZone = (z: SignatureZone, canvas: HTMLCanvasElement) => {
const selectZone = async (z: SignatureZone, canvas: HTMLCanvasElement) => {
userSignatureZone.value = z;
const ctx = canvas.getContext("2d");
if (ctx) {
setPage(page.value);
setTimeout(() => drawAllZones(page.value), 200);
await resetPages();
setTimeout(drawAllZones, 200);
}
};
const selectZoneEvent = (e: PointerEvent, canvas: HTMLCanvasElement) =>
signature.zones
.filter((z) => z.PDFPage.index + 1 === page.value)
.filter(
(z) =>
(z.PDFPage.index + 1 === getCanvasId(canvas) && multiPage.value) ||
(z.PDFPage.index + 1 === page.value && !multiPage.value)
)
.map((z) => {
if (
hitSignature(z, [e.offsetX, e.offsetY], canvas.width, canvas.height)
) {
if (hitSignature(z, [e.offsetX, e.offsetY], canvas)) {
if (userSignatureZone.value === null) {
selectZone(z, canvas);
} else {
@ -466,18 +551,20 @@ const selectZoneEvent = (e: PointerEvent, canvas: HTMLCanvasElement) =>
}
});
const canvasClick = (e: PointerEvent) => {
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement;
const canvasClick = (e: PointerEvent, canvas: HTMLCanvasElement) =>
canvasEvent.value === "select"
? selectZoneEvent(e, canvas)
: addZoneEvent(e, canvas);
};
const turnPage = async (upOrDown: number) => {
//userSignatureZone.value = null; // desactivate the reset of the zone when turning page
page.value = page.value + upOrDown;
await setPage(page.value);
setTimeout(() => drawAllZones(page.value), 200);
if (multiPage.value) {
const canvas = getCanvas(page.value);
canvas.scrollIntoView();
} else {
await setPage(page.value);
setTimeout(drawAllZones, 200);
}
};
const turnSignature = async (upOrDown: number) => {
@ -493,9 +580,9 @@ const turnSignature = async (upOrDown: number) => {
let currentZone = signature.zones[zoneIndex];
if (currentZone) {
page.value = currentZone.PDFPage.index + 1;
userSignatureZone.value = currentZone;
const canvas = document.querySelectorAll("canvas")[0];
const canvas = getCanvas(currentZone.PDFPage.index + 1);
selectZone(currentZone, canvas);
canvas.scrollIntoView();
}
};
@ -540,19 +627,25 @@ const drawZone = (
}
};
const drawAllZones = (page: number) => {
const canvas = document.querySelectorAll("canvas")[0];
const ctx = canvas.getContext("2d");
if (ctx && signedState.value !== "signed") {
const drawAllZones = () => {
if (signedState.value !== "signed") {
signature.zones
.filter((z) => z.PDFPage.index + 1 === page)
.filter(
(z) =>
multiPage.value ||
(z.PDFPage.index + 1 === page.value && !multiPage.value)
)
.map((z) => {
if (userSignatureZone.value) {
if (userSignatureZone.value?.index === z.index) {
const canvas = getCanvas(z.PDFPage.index + 1);
const ctx = canvas.getContext("2d");
if (ctx) {
if (userSignatureZone.value) {
if (userSignatureZone.value?.index === z.index) {
drawZone(z, ctx, canvas.width, canvas.height);
}
} else {
drawZone(z, ctx, canvas.width, canvas.height);
}
} else {
drawZone(z, ctx, canvas.width, canvas.height);
}
});
}
@ -638,8 +731,8 @@ const confirmSign = () => {
const undoSign = async () => {
signature.zones = signature.zones.filter((z) => z.index !== null);
await setPage(page.value);
setTimeout(() => drawAllZones(page.value), 200);
await resetPages();
setTimeout(drawAllZones, 200);
userSignatureZone.value = null;
adding.value = false;
canvasEvent.value = "select";
@ -671,7 +764,7 @@ const addZoneEvent = async (e: PointerEvent, canvas: HTMLCanvasElement) => {
width: BOX_WIDTH * zoom.value,
height: BOX_HEIGHT * zoom.value,
PDFPage: {
index: page.value - 1,
index: multiPage.value ? getCanvasId(canvas) - 1 : page.value - 1,
width: PDFPageWidth,
height: PDFPageHeight,
},
@ -679,8 +772,8 @@ const addZoneEvent = async (e: PointerEvent, canvas: HTMLCanvasElement) => {
signature.zones.push(newZone);
userSignatureZone.value = newZone;
await setPage(page.value);
setTimeout(() => drawAllZones(page.value), 200);
await resetPages();
setTimeout(drawAllZones, 200);
canvasEvent.value = "select";
adding.value = true;
};
@ -695,14 +788,15 @@ init();
</script>
<style scoped lang="scss">
#canvas {
canvas {
box-shadow: 0 20px 20px rgba(0, 0, 0, 0.2);
margin: 1rem auto;
}
.onAddZone {
cursor: not-allowed;
#canvas {
canvas {
cursor: copy;
}
}
@ -716,6 +810,10 @@ div#action-buttons {
div.pdf-tools {
background-color: #f3f3f3;
font-size: 0.6rem;
label {
font-size: 0.75rem !important;
margin: auto 0 auto 0.3rem;
}
button {
font-size: 0.75rem !important;
}

View File

@ -24,6 +24,8 @@ const appMessages = {
loading: 'Chargement...',
remove_sign_zone: 'Enlever la zone',
return: 'Retour',
see_all_pages: 'Voir toutes les pages',
all_pages: 'Toutes les pages',
}
}