mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
938 lines
30 KiB
Vue
938 lines
30 KiB
Vue
<template>
|
||
<teleport to="body">
|
||
<modal v-if="modalOpen" @close="modalOpen = false">
|
||
<template v-slot:header>
|
||
<h2>{{ trans(SIGNATURES_SIGNATURE_CONFIRMATION) }}</h2>
|
||
</template>
|
||
<template v-slot:body>
|
||
<div class="signature-modal-body text-center" v-if="loading">
|
||
<p>
|
||
{{ trans(SIGNATURES_ELECTRONIC_SIGNATURE_IN_PROGRESS) }}
|
||
</p>
|
||
<div class="loading">
|
||
<i
|
||
class="fa fa-circle-o-notch fa-spin fa-3x"
|
||
:title="trans(SIGNATURES_LOADING)"
|
||
></i>
|
||
</div>
|
||
</div>
|
||
<div class="signature-modal-body text-center" v-else>
|
||
<p>{{ trans(SIGNATURES_YOU_ARE_GOING_TO_SIGN) }}</p>
|
||
<p>{{ trans(SIGNATURES_ARE_YOU_SURE) }}</p>
|
||
</div>
|
||
</template>
|
||
<template v-slot:footer>
|
||
<button class="btn btn-action" @click.prevent="confirmSign">
|
||
{{ trans(SIGNATURES_YES) }}
|
||
</button>
|
||
</template>
|
||
</modal>
|
||
</teleport>
|
||
<div class="col-12 m-auto sticky-top">
|
||
<div
|
||
class="row justify-content-center border-bottom pdf-tools d-md-none"
|
||
>
|
||
<div class="col-5 text-center turn-page">
|
||
<select
|
||
class="form-select form-select-sm"
|
||
id="zoomSelect"
|
||
v-model="zoomLevel"
|
||
@change="setZoomLevel(zoomLevel)"
|
||
>
|
||
<option value="" selected disabled>Zoom</option>
|
||
<option v-for="z in zoomLevels" :value="z.zoom" :key="z.id">
|
||
{{ z.label.fr }}
|
||
</option>
|
||
</select>
|
||
<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>
|
||
</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">
|
||
{{ trans(SIGNATURES_ALL_PAGES) }}
|
||
</label>
|
||
</template>
|
||
</div>
|
||
<div
|
||
v-if="signature.zones.length === 1 && signedState !== 'signed'"
|
||
class="col-5 p-0 text-center turnSignature"
|
||
>
|
||
<button
|
||
class="btn btn-light btn-sm"
|
||
@click="goToSignatureZoneUnique"
|
||
>
|
||
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
|
||
</button>
|
||
</div>
|
||
<div
|
||
v-if="signature.zones.length > 1"
|
||
class="col-5 p-0 text-center turnSignature"
|
||
>
|
||
<button
|
||
:disabled="isFirstSignatureZone()"
|
||
class="btn btn-light btn-sm"
|
||
@click="turnSignature(-1)"
|
||
>
|
||
{{ trans(SIGNATURES_LAST_ZONE) }}
|
||
</button>
|
||
<span>|</span>
|
||
<button
|
||
:disabled="isLastSignatureZone()"
|
||
class="btn btn-light btn-sm"
|
||
@click="turnSignature(1)"
|
||
>
|
||
{{ trans(SIGNATURES_NEXT_ZONE) }}
|
||
</button>
|
||
</div>
|
||
<div class="col text-end" v-if="signedState !== 'signed'">
|
||
<button
|
||
class="btn btn-misc btn-sm"
|
||
:hidden="!userSignatureZone"
|
||
@click="undoSign"
|
||
v-if="signature.zones.length > 1"
|
||
:title="trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE)"
|
||
>
|
||
{{ trans(SIGNATURES_ANOTHER_ZONE) }}
|
||
</button>
|
||
<button
|
||
class="btn btn-misc btn-sm"
|
||
:hidden="!userSignatureZone"
|
||
@click="undoSign"
|
||
v-else
|
||
>
|
||
{{ trans(SIGNATURES_CANCEL) }}
|
||
</button>
|
||
<button
|
||
v-if="userSignatureZone === null"
|
||
:class="{
|
||
btn: true,
|
||
'btn-sm': true,
|
||
'btn-create': canvasEvent !== 'add',
|
||
'btn-chill-green': canvasEvent === 'add',
|
||
active: canvasEvent === 'add',
|
||
}"
|
||
@click="toggleAddZone()"
|
||
:title="trans(SIGNATURES_ADD_SIGN_ZONE)"
|
||
>
|
||
<template v-if="canvasEvent === 'add'">
|
||
<div
|
||
class="spinner-border spinner-border-sm"
|
||
role="status"
|
||
>
|
||
<span class="visually-hidden">Loading...</span>
|
||
</div>
|
||
</template>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="row justify-content-center border-bottom pdf-tools d-none d-md-flex"
|
||
>
|
||
<div class="col-5 text-center turn-page ps-3">
|
||
<select
|
||
class="form-select form-select-sm"
|
||
id="zoomSelect"
|
||
v-model="zoomLevel"
|
||
@change="setZoomLevel(zoomLevel)"
|
||
>
|
||
<option value="" selected disabled>Zoom</option>
|
||
<option v-for="z in zoomLevels" :value="z.zoom" :key="z.id">
|
||
{{ z.label.fr }}
|
||
</option>
|
||
</select>
|
||
<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">
|
||
{{ trans(SIGNATURES_SEE_ALL_PAGES) }}
|
||
</label>
|
||
</template>
|
||
</div>
|
||
<div
|
||
v-if="signature.zones.length === 1 && signedState !== 'signed'"
|
||
class="col-4 d-xl-none text-center turnSignature p-0"
|
||
>
|
||
<button
|
||
class="btn btn-light btn-sm"
|
||
@click="goToSignatureZoneUnique"
|
||
>
|
||
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
|
||
</button>
|
||
</div>
|
||
<div
|
||
v-if="signature.zones.length > 1 && signedState !== 'signed'"
|
||
class="col-4 d-xl-none text-center turnSignature p-0"
|
||
>
|
||
<button
|
||
:disabled="isFirstSignatureZone()"
|
||
class="btn btn-light btn-sm"
|
||
@click="turnSignature(-1)"
|
||
>
|
||
{{ trans(SIGNATURES_LAST_ZONE) }}
|
||
</button>
|
||
<span>|</span>
|
||
<button
|
||
:disabled="isLastSignatureZone()"
|
||
class="btn btn-light btn-sm"
|
||
@click="turnSignature(1)"
|
||
>
|
||
{{ trans(SIGNATURES_NEXT_ZONE) }}
|
||
</button>
|
||
</div>
|
||
<div
|
||
v-if="signature.zones.length === 1 && signedState !== 'signed'"
|
||
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
|
||
>
|
||
<button
|
||
class="btn btn-light btn-sm"
|
||
@click="goToSignatureZoneUnique"
|
||
>
|
||
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
|
||
</button>
|
||
</div>
|
||
<div
|
||
v-if="signature.zones.length > 1 && signedState !== 'signed'"
|
||
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
|
||
>
|
||
<button
|
||
:disabled="isFirstSignatureZone()"
|
||
class="btn btn-light btn-sm"
|
||
@click="turnSignature(-1)"
|
||
>
|
||
{{ trans(SIGNATURES_LAST_SIGN_ZONE) }}
|
||
</button>
|
||
<span>|</span>
|
||
<button
|
||
:disabled="isLastSignatureZone()"
|
||
class="btn btn-light btn-sm"
|
||
@click="turnSignature(1)"
|
||
>
|
||
{{ trans(SIGNATURES_NEXT_SIGN_ZONE) }}
|
||
</button>
|
||
</div>
|
||
<div class="col text-end" v-if="signedState !== 'signed'">
|
||
<button
|
||
class="btn btn-misc btn-sm"
|
||
:hidden="!userSignatureZone"
|
||
@click="undoSign"
|
||
v-if="signature.zones.length > 1"
|
||
>
|
||
{{ trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE) }}
|
||
</button>
|
||
<button
|
||
class="btn btn-misc btn-sm"
|
||
:hidden="!userSignatureZone"
|
||
@click="undoSign"
|
||
v-else
|
||
>
|
||
{{ trans(SIGNATURES_CANCEL) }}
|
||
</button>
|
||
<button
|
||
v-if="userSignatureZone === null"
|
||
:class="{
|
||
btn: true,
|
||
'btn-sm': true,
|
||
'btn-create': canvasEvent !== 'add',
|
||
'btn-chill-green': canvasEvent === 'add',
|
||
active: canvasEvent === 'add',
|
||
}"
|
||
@click="toggleAddZone()"
|
||
:title="trans(SIGNATURES_ADD_SIGN_ZONE)"
|
||
>
|
||
<template v-if="canvasEvent !== 'add'">
|
||
{{ trans(SIGNATURES_ADD_ZONE) }}
|
||
</template>
|
||
<template v-else>
|
||
{{ trans(SIGNATURES_CLICK_ON_DOCUMENT) }}
|
||
<div
|
||
class="spinner-border spinner-border-sm"
|
||
role="status"
|
||
>
|
||
<span class="visually-hidden">Loading...</span>
|
||
</div>
|
||
</template>
|
||
</button>
|
||
</div>
|
||
</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">
|
||
<a
|
||
class="btn btn-cancel"
|
||
v-if="signedState !== 'signed'"
|
||
:href="getReturnPath()"
|
||
>
|
||
{{ trans(SIGNATURES_CANCEL) }}
|
||
</a>
|
||
<a class="btn btn-misc" v-else :href="getReturnPath()">
|
||
{{ trans(SIGNATURES_RETURN) }}
|
||
</a>
|
||
</div>
|
||
<div class="col text-end" v-if="signedState !== 'signed'">
|
||
<button
|
||
class="btn btn-action me-2"
|
||
:disabled="!userSignatureZone"
|
||
@click="sign"
|
||
>
|
||
{{ trans(SIGNATURES_SIGN) }}
|
||
</button>
|
||
</div>
|
||
<div class="col-4" v-else></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, Ref } from "vue";
|
||
import { useToast } from "vue-toast-notification";
|
||
import "vue-toast-notification/dist/theme-sugar.css";
|
||
import {
|
||
CanvasEvent,
|
||
CheckSignature,
|
||
Signature,
|
||
SignatureZone,
|
||
SignedState,
|
||
ZoomLevel,
|
||
} from "../../types";
|
||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||
import * as pdfjsLib from "pdfjs-dist";
|
||
import {
|
||
PDFDocumentProxy,
|
||
PDFPageProxy,
|
||
} from "pdfjs-dist/types/src/display/api";
|
||
|
||
import {
|
||
SIGNATURES_YES,
|
||
SIGNATURES_ARE_YOU_SURE,
|
||
SIGNATURES_YOU_ARE_GOING_TO_SIGN,
|
||
SIGNATURES_SIGNATURE_CONFIRMATION,
|
||
SIGNATURES_SIGN,
|
||
SIGNATURES_CHOOSE_ANOTHER_SIGNATURE,
|
||
SIGNATURES_CANCEL,
|
||
SIGNATURES_LAST_SIGN_ZONE,
|
||
SIGNATURES_NEXT_SIGN_ZONE,
|
||
SIGNATURES_ADD_SIGN_ZONE,
|
||
SIGNATURES_CLICK_ON_DOCUMENT,
|
||
SIGNATURES_LAST_ZONE,
|
||
SIGNATURES_NEXT_ZONE,
|
||
SIGNATURES_ADD_ZONE,
|
||
SIGNATURES_ANOTHER_ZONE,
|
||
SIGNATURES_ELECTRONIC_SIGNATURE_IN_PROGRESS,
|
||
SIGNATURES_LOADING,
|
||
SIGNATURES_RETURN,
|
||
SIGNATURES_SEE_ALL_PAGES,
|
||
SIGNATURES_ALL_PAGES,
|
||
SIGNATURES_GO_TO_SIGNATURE_UNIQUE,
|
||
trans,
|
||
} from "translator";
|
||
|
||
// @ts-ignore incredible but the console.log is needed
|
||
import * as PdfWorker from "pdfjs-dist/build/pdf.worker.mjs";
|
||
console.log(PdfWorker);
|
||
|
||
// import { PdfWorker } from 'pdfjs-dist/build/pdf.worker.mjs'
|
||
// pdfjsLib.GlobalWorkerOptions.workerSrc = PdfWorker;
|
||
|
||
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||
import { download_doc_as_pdf } from "../StoredObjectButton/helpers";
|
||
|
||
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);
|
||
const canvasEvent: Ref<CanvasEvent> = ref("select");
|
||
const signedState: Ref<SignedState> = ref("pending");
|
||
const page: Ref<number> = ref(1);
|
||
const pageCount: Ref<number> = ref(0);
|
||
const zoom: Ref<number> = ref(1);
|
||
let zoomLevel = "";
|
||
const zoomLevels: Ref<ZoomLevel[]> = ref([
|
||
{
|
||
id: 0,
|
||
zoom: 0.75,
|
||
label: {
|
||
fr: "75%",
|
||
},
|
||
},
|
||
{
|
||
id: 1,
|
||
zoom: zoom.value,
|
||
label: {
|
||
fr: "100%",
|
||
},
|
||
},
|
||
{
|
||
id: 2,
|
||
zoom: 1.25,
|
||
label: {
|
||
fr: "125%",
|
||
},
|
||
},
|
||
{
|
||
id: 3,
|
||
zoom: 1.5,
|
||
label: {
|
||
fr: "150%",
|
||
},
|
||
},
|
||
{
|
||
id: 4,
|
||
zoom: 2,
|
||
label: {
|
||
fr: "200%",
|
||
},
|
||
},
|
||
{
|
||
id: 5,
|
||
zoom: 3,
|
||
label: {
|
||
fr: "300%",
|
||
},
|
||
},
|
||
]);
|
||
let userSignatureZone: Ref<null | SignatureZone> = ref(null);
|
||
let pdf = {} as PDFDocumentProxy;
|
||
|
||
declare global {
|
||
interface Window {
|
||
signature: Signature;
|
||
}
|
||
}
|
||
|
||
const $toast = useToast();
|
||
|
||
const signature = window.signature;
|
||
|
||
const isFirstSignatureZone = () =>
|
||
userSignatureZone.value?.index != null
|
||
? userSignatureZone.value.index < 1
|
||
: false;
|
||
|
||
const isLastSignatureZone = () =>
|
||
userSignatureZone.value?.index
|
||
? userSignatureZone.value.index >= signature.zones.length - 1
|
||
: false;
|
||
|
||
const setZoomLevel = async (zoomLevel: string) => {
|
||
zoom.value = Number.parseFloat(zoomLevel);
|
||
await resetPages();
|
||
setTimeout(drawAllZones, 200);
|
||
};
|
||
|
||
const mountPdf = async (doc: ArrayBuffer) => {
|
||
const loadingTask = pdfjsLib.getDocument(doc);
|
||
pdf = await loadingTask.promise;
|
||
pageCount.value = pdf.numPages;
|
||
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 = getCanvas(pdfPage.pageNumber);
|
||
const context = canvas.getContext("2d") as CanvasRenderingContext2D;
|
||
canvas.height = viewport.height;
|
||
canvas.width = viewport.width;
|
||
|
||
return {
|
||
canvasContext: context,
|
||
viewport: viewport,
|
||
};
|
||
};
|
||
|
||
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);
|
||
await pdfPage.render(renderContext);
|
||
};
|
||
|
||
const init = () => downloadAndOpen().then(initPdf);
|
||
|
||
async function downloadAndOpen(): Promise<Blob> {
|
||
let raw;
|
||
try {
|
||
raw = await download_doc_as_pdf(signature.storedObject);
|
||
} catch (e) {
|
||
console.error("error while downloading and decrypting document", e);
|
||
throw e;
|
||
}
|
||
const doc = await raw.arrayBuffer();
|
||
await mountPdf(doc);
|
||
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 = () => {
|
||
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) =>
|
||
Math.round((x * canvasWidth) / PDFWidth);
|
||
|
||
const scaleYToCanvas = (h: number, canvasHeight: number, PDFHeight: number) =>
|
||
Math.round((h * canvasHeight) / PDFHeight);
|
||
|
||
const hitSignature = (
|
||
zone: SignatureZone,
|
||
xy: number[],
|
||
canvas: HTMLCanvasElement,
|
||
) =>
|
||
scaleXToCanvas(zone.x, canvas.width, zone.PDFPage.width) < xy[0] &&
|
||
xy[0] <
|
||
scaleXToCanvas(zone.x + zone.width, canvas.width, zone.PDFPage.width) &&
|
||
zone.PDFPage.height * zoom.value -
|
||
scaleYToCanvas(zone.y, canvas.height, zone.PDFPage.height) <
|
||
xy[1] &&
|
||
xy[1] <
|
||
scaleYToCanvas(
|
||
zone.height - zone.y,
|
||
canvas.height,
|
||
zone.PDFPage.height,
|
||
) +
|
||
zone.PDFPage.height * zoom.value;
|
||
|
||
const selectZone = async (z: SignatureZone, canvas: HTMLCanvasElement) => {
|
||
userSignatureZone.value = z;
|
||
const ctx = canvas.getContext("2d");
|
||
if (ctx) {
|
||
await resetPages();
|
||
setTimeout(drawAllZones, 200);
|
||
}
|
||
};
|
||
|
||
const selectZoneEvent = (e: PointerEvent, canvas: HTMLCanvasElement) =>
|
||
signature.zones
|
||
.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)) {
|
||
if (userSignatureZone.value === null) {
|
||
selectZone(z, canvas);
|
||
} else {
|
||
if (userSignatureZone.value.index === z.index) {
|
||
sign();
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
const canvasClick = (e: PointerEvent, canvas: HTMLCanvasElement) =>
|
||
canvasEvent.value === "select"
|
||
? selectZoneEvent(e, canvas)
|
||
: addZoneEvent(e, canvas);
|
||
|
||
const turnPage = async (upOrDown: number) => {
|
||
page.value = page.value + upOrDown;
|
||
if (multiPage.value) {
|
||
const canvas = getCanvas(page.value);
|
||
canvas.scrollIntoView();
|
||
} else {
|
||
await setPage(page.value);
|
||
setTimeout(drawAllZones, 200);
|
||
}
|
||
};
|
||
|
||
const selectZoneInCanvas = (signatureZone: SignatureZone) => {
|
||
page.value = signatureZone.PDFPage.index + 1;
|
||
const canvas = getCanvas(signatureZone.PDFPage.index + 1);
|
||
selectZone(signatureZone, canvas);
|
||
canvas.scrollIntoView();
|
||
};
|
||
|
||
const goToSignatureZoneUnique = () => selectZoneInCanvas(signature.zones[0]);
|
||
|
||
const turnSignature = async (upOrDown: number) => {
|
||
let zoneIndex = userSignatureZone.value?.index ?? -1;
|
||
if (zoneIndex < -1) {
|
||
zoneIndex = -1;
|
||
}
|
||
if (zoneIndex < signature.zones.length) {
|
||
zoneIndex = zoneIndex + upOrDown;
|
||
} else {
|
||
zoneIndex = 0;
|
||
}
|
||
let currentZone = signature.zones[zoneIndex];
|
||
if (currentZone) {
|
||
selectZoneInCanvas(currentZone);
|
||
}
|
||
};
|
||
|
||
const drawZone = (
|
||
zone: SignatureZone,
|
||
ctx: CanvasRenderingContext2D,
|
||
canvasWidth: number,
|
||
canvasHeight: number,
|
||
) => {
|
||
const unselectedBlue = "#007bff";
|
||
const selectedBlue = "#034286";
|
||
ctx.strokeStyle =
|
||
userSignatureZone.value?.index === zone.index
|
||
? selectedBlue
|
||
: unselectedBlue;
|
||
ctx.lineWidth = 2;
|
||
ctx.lineJoin = "bevel";
|
||
ctx.strokeRect(
|
||
scaleXToCanvas(zone.x, canvasWidth, zone.PDFPage.width),
|
||
zone.PDFPage.height * zoom.value -
|
||
scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height),
|
||
scaleXToCanvas(zone.width, canvasWidth, zone.PDFPage.width),
|
||
scaleYToCanvas(zone.height, canvasHeight, zone.PDFPage.height),
|
||
);
|
||
ctx.font = `bold ${16 * zoom.value}px serif`;
|
||
ctx.textAlign = "center";
|
||
ctx.fillStyle = "black";
|
||
const xText =
|
||
scaleXToCanvas(zone.x, canvasWidth, zone.PDFPage.width) +
|
||
scaleXToCanvas(zone.width, canvasWidth, zone.PDFPage.width) / 2;
|
||
const yText =
|
||
zone.PDFPage.height * zoom.value -
|
||
scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height) +
|
||
scaleYToCanvas(zone.height, canvasHeight, zone.PDFPage.height) / 2;
|
||
if (userSignatureZone.value?.index === zone.index) {
|
||
ctx.fillStyle = selectedBlue;
|
||
ctx.fillText("Signer ici", xText, yText);
|
||
} else {
|
||
ctx.fillStyle = unselectedBlue;
|
||
ctx.fillText("Choisir cette", xText, yText - 12 * zoom.value);
|
||
ctx.fillText("zone de signature", xText, yText + 12 * zoom.value);
|
||
}
|
||
};
|
||
|
||
const drawAllZones = () => {
|
||
if (signedState.value !== "signed") {
|
||
signature.zones
|
||
.filter(
|
||
(z) =>
|
||
multiPage.value ||
|
||
(z.PDFPage.index + 1 === page.value && !multiPage.value),
|
||
)
|
||
.map((z) => {
|
||
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);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
const checkSignature = () => {
|
||
const url = `/api/1.0/document/workflow/${signature.id}/check-signature`;
|
||
return makeFetch<null, CheckSignature>("GET", url)
|
||
.then((r) => {
|
||
signedState.value = r.state;
|
||
signature.storedObject = r.storedObject;
|
||
checkForReady();
|
||
})
|
||
.catch((error) => {
|
||
signedState.value = "error";
|
||
console.log("Error while checking the signature", error);
|
||
$toast.error(
|
||
`Erreur lors de la vérification de la signature: ${error.txt}`,
|
||
);
|
||
});
|
||
};
|
||
|
||
const maxTryForReady = 60; //2 minutes for trying to sign
|
||
let tryForReady = 0;
|
||
|
||
const stopTrySigning = () => {
|
||
loading.value = false;
|
||
modalOpen.value = false;
|
||
};
|
||
|
||
const checkForReady = () => {
|
||
if (tryForReady > maxTryForReady) {
|
||
stopTrySigning();
|
||
tryForReady = 0;
|
||
console.log("Reached the maximum number of tentative to try signing");
|
||
$toast.error(
|
||
"Le nombre maximum de tentatives pour essayer de signer est atteint",
|
||
);
|
||
}
|
||
if (signedState.value === "rejected") {
|
||
stopTrySigning();
|
||
console.log("Signature rejected by the server");
|
||
$toast.error("Signature rejetée par le serveur");
|
||
}
|
||
if (signedState.value === "canceled") {
|
||
stopTrySigning();
|
||
console.log("Signature canceled");
|
||
$toast.error("Signature annulée");
|
||
}
|
||
if (signedState.value === "pending") {
|
||
tryForReady = tryForReady + 1;
|
||
setTimeout(() => checkSignature(), 2000);
|
||
} else {
|
||
stopTrySigning();
|
||
if (signedState.value === "signed") {
|
||
userSignatureZone.value = null;
|
||
downloadAndOpen();
|
||
}
|
||
}
|
||
};
|
||
|
||
const sign = () => (modalOpen.value = true);
|
||
|
||
const confirmSign = () => {
|
||
loading.value = true;
|
||
const url = `/api/1.0/document/workflow/${signature.id}/signature-request`;
|
||
const body = {
|
||
storedObject: signature.storedObject,
|
||
zone: userSignatureZone.value,
|
||
};
|
||
makeFetch("POST", url, body)
|
||
.then(() => {
|
||
checkForReady();
|
||
})
|
||
.catch((error) => {
|
||
console.log("Error while posting the signature", error);
|
||
stopTrySigning();
|
||
$toast.error(
|
||
`Erreur lors de la soumission de la signature: ${error.txt}`,
|
||
);
|
||
});
|
||
};
|
||
|
||
const undoSign = async () => {
|
||
signature.zones = signature.zones.filter((z) => z.index !== null);
|
||
await resetPages();
|
||
setTimeout(drawAllZones, 200);
|
||
userSignatureZone.value = null;
|
||
adding.value = false;
|
||
canvasEvent.value = "select";
|
||
};
|
||
|
||
const toggleAddZone = () => {
|
||
canvasEvent.value = canvasEvent.value === "select" ? "add" : "select";
|
||
};
|
||
|
||
const addZoneEvent = async (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 * zoom.value -
|
||
scaleYToCanvas(y, canvas.height, PDFPageHeight) +
|
||
scaleYToCanvas(BOX_HEIGHT / 2, canvas.height, PDFPageHeight),
|
||
width: BOX_WIDTH * zoom.value,
|
||
height: BOX_HEIGHT * zoom.value,
|
||
PDFPage: {
|
||
index: multiPage.value ? getCanvasId(canvas) - 1 : page.value - 1,
|
||
width: PDFPageWidth,
|
||
height: PDFPageHeight,
|
||
},
|
||
};
|
||
signature.zones.push(newZone);
|
||
userSignatureZone.value = newZone;
|
||
|
||
await resetPages();
|
||
setTimeout(drawAllZones, 200);
|
||
canvasEvent.value = "select";
|
||
adding.value = true;
|
||
};
|
||
|
||
const getReturnPath = () =>
|
||
window.location.search
|
||
? (window.location.search.split("?returnPath=")[1] ??
|
||
window.location.pathname)
|
||
: window.location.pathname;
|
||
|
||
init();
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
canvas {
|
||
box-shadow: 0 20px 20px rgba(0, 0, 0, 0.2);
|
||
margin: 1rem auto;
|
||
}
|
||
|
||
.onAddZone {
|
||
cursor: not-allowed;
|
||
|
||
canvas {
|
||
cursor: copy;
|
||
}
|
||
}
|
||
|
||
div#action-buttons {
|
||
position: sticky;
|
||
bottom: 0px;
|
||
background-color: white;
|
||
z-index: 100;
|
||
}
|
||
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;
|
||
}
|
||
div.turnSignature {
|
||
span {
|
||
font-size: 1rem;
|
||
}
|
||
}
|
||
@media (min-width: 1400px) {
|
||
// background: none;
|
||
// border: none !important;
|
||
}
|
||
}
|
||
div.turn-page {
|
||
display: flex;
|
||
span {
|
||
font-size: 0.75rem;
|
||
margin: auto 0.4rem;
|
||
}
|
||
select {
|
||
width: 5rem;
|
||
font-size: 0.75rem;
|
||
}
|
||
}
|
||
div.signature-modal-body {
|
||
height: 8rem;
|
||
}
|
||
</style>
|