refactor: replace pdfjs-dist with native browser iframe for PDF preview
Remove pdfjs-dist dependency and simplify PdfPreview component to use a native browser iframe, reducing bundle size and complexity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
263
frontend/package-lock.json
generated
263
frontend/package-lock.json
generated
@@ -9,7 +9,6 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.1",
|
||||||
"pdfjs-dist": "^4.0.379",
|
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-chartjs": "^5.3.0",
|
"vue-chartjs": "^5.3.0",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4"
|
||||||
@@ -516,256 +515,6 @@
|
|||||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-/p08f93LEbsL5mDZFQ3DBxcPv/I4QG9EDYRRq1WNlCOXVfAHBTHMSVMwxlqG/AtnSfUr9+vgfN7MKiyDo0+Weg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"workspaces": [
|
|
||||||
"e2e/*"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@napi-rs/canvas-android-arm64": "0.1.88",
|
|
||||||
"@napi-rs/canvas-darwin-arm64": "0.1.88",
|
|
||||||
"@napi-rs/canvas-darwin-x64": "0.1.88",
|
|
||||||
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.88",
|
|
||||||
"@napi-rs/canvas-linux-arm64-gnu": "0.1.88",
|
|
||||||
"@napi-rs/canvas-linux-arm64-musl": "0.1.88",
|
|
||||||
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.88",
|
|
||||||
"@napi-rs/canvas-linux-x64-gnu": "0.1.88",
|
|
||||||
"@napi-rs/canvas-linux-x64-musl": "0.1.88",
|
|
||||||
"@napi-rs/canvas-win32-arm64-msvc": "0.1.88",
|
|
||||||
"@napi-rs/canvas-win32-x64-msvc": "0.1.88"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-android-arm64": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-KEaClPnZuVxJ8smUWjV1wWFkByBO/D+vy4lN+Dm5DFH514oqwukxKGeck9xcKJhaWJGjfruGmYGiwRe//+/zQQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-Xgywz0dDxOKSgx3eZnK85WgGMmGrQEW7ZLA/E7raZdlEE+xXCozobgqz2ZvYigpB6DJFYkqnwHjqCOTSDGlFdg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-darwin-x64": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-Yz4wSCIQOUgNucgk+8NFtQxQxZV5NO8VKRl9ePKE6XoNyNVC8JDqtvhh3b3TPqKK8W5p2EQpAr1rjjm0mfBxdg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-9gQM2SlTo76hYhxHi2XxWTAqpTOb+JtxMPEIr+H5nAhHhyEtNmTSDRtz93SP7mGd2G3Ojf2oF5tP9OdgtgXyKg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-7qgaOBMXuVRk9Fzztzr3BchQKXDxGbY+nwsovD3I/Sx81e+sX0ReEDYHTItNb0Je4NHbAl7D0MKyd4SvUc04sg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-kYyNrUsHLkoGHBc77u4Unh067GrfiCUMbGHC2+OTxbeWfZkPt2o32UOQkhnSswKd9Fko/wSqqGkY956bIUzruA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-HVuH7QgzB0yavYdNZDRyAsn/ejoXB0hn8twwFnOqUbCCdkV+REna7RXjSR7+PdfW0qMQ2YYWsLvVBT5iL/mGpw==",
|
|
||||||
"cpu": [
|
|
||||||
"riscv64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-hvcvKIcPEQrvvJtJnwD35B3qk6umFJ8dFIr8bSymfrSMem0EQsfn1ztys8ETIFndTwdNWJKWluvxztA41ivsEw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-eSMpGYY2xnZSQ6UxYJ6plDboxq4KeJ4zT5HaVkUnbObNN6DlbJe0Mclh3wifAmquXfrlgTZt6zhHsUgz++AK6g==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-qcIFfEgHrchyYqRrxsCeTQgpJZ/GqHiqPcU/Fvw/ARVlQeDX1VyFH+X+0gCR2tca6UJrq96vnW+5o7buCq+erA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
|
||||||
"version": "0.1.88",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.88.tgz",
|
|
||||||
"integrity": "sha512-ROVqbfS4QyZxYkqmaIBBpbz/BQvAR+05FXM5PAtTYVc0uyY8Y4BHJSMdGAaMf6TdIVRsQsiq+FG/dH9XhvWCFQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -1931,18 +1680,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pdfjs-dist": {
|
|
||||||
"version": "4.10.38",
|
|
||||||
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz",
|
|
||||||
"integrity": "sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@napi-rs/canvas": "^0.1.65"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.1",
|
||||||
"pdfjs-dist": "^4.0.379",
|
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-chartjs": "^5.3.0",
|
"vue-chartjs": "^5.3.0",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4"
|
||||||
|
|||||||
@@ -1,112 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full bg-gray-800 overflow-hidden relative">
|
<div class="flex flex-col h-full bg-gray-800 overflow-hidden relative">
|
||||||
<!-- Header with navigation and zoom -->
|
<!-- Header -->
|
||||||
<div class="flex-shrink-0 flex items-center justify-between px-3 py-2 bg-gray-900 text-white">
|
<div class="flex-shrink-0 flex items-center px-3 py-2 bg-gray-900 text-white">
|
||||||
<!-- File name -->
|
<span class="text-sm font-medium truncate">{{ fileName }}</span>
|
||||||
<span class="text-sm font-medium truncate max-w-[150px]">{{ fileName }}</span>
|
|
||||||
|
|
||||||
<!-- Controls -->
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<!-- Zoom controls -->
|
|
||||||
<div class="flex items-center gap-1">
|
|
||||||
<button
|
|
||||||
@click="zoomOut"
|
|
||||||
:disabled="zoom <= 0.5"
|
|
||||||
class="p-1 rounded hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
title="Zoom arriere"
|
|
||||||
>
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<span class="text-xs w-12 text-center">{{ Math.round(zoom * 100) }}%</span>
|
|
||||||
<button
|
|
||||||
@click="zoomIn"
|
|
||||||
:disabled="zoom >= 3"
|
|
||||||
class="p-1 rounded hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
title="Zoom avant"
|
|
||||||
>
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="fitToWidth"
|
|
||||||
class="p-1 rounded hover:bg-gray-700 text-xs ml-1"
|
|
||||||
title="Ajuster a la largeur"
|
|
||||||
>
|
|
||||||
Fit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Page navigation -->
|
|
||||||
<div v-if="totalPages > 0" class="flex items-center gap-1">
|
|
||||||
<button
|
|
||||||
@click="prevPage"
|
|
||||||
:disabled="currentPage <= 1"
|
|
||||||
class="p-1 rounded hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<span class="text-xs w-16 text-center">{{ currentPage }} / {{ totalPages }}</span>
|
|
||||||
<button
|
|
||||||
@click="nextPage"
|
|
||||||
:disabled="currentPage >= totalPages"
|
|
||||||
class="p-1 rounded hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PDF Canvas Container -->
|
<!-- PDF via native browser viewer -->
|
||||||
<div
|
<iframe
|
||||||
ref="containerRef"
|
v-if="pdfUrl"
|
||||||
class="flex-1 overflow-auto bg-gray-700"
|
:src="pdfUrl"
|
||||||
@wheel.ctrl.prevent="onWheel"
|
type="application/pdf"
|
||||||
>
|
class="flex-1 w-full border-0"
|
||||||
<div class="inline-block min-w-full min-h-full p-4">
|
/>
|
||||||
<div class="flex justify-center">
|
|
||||||
<canvas ref="canvasRef" class="shadow-xl bg-white block"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading overlay -->
|
<!-- Empty state -->
|
||||||
<div v-if="isLoading" class="absolute inset-0 flex items-center justify-center bg-gray-800/90">
|
<div v-else class="flex-1 flex items-center justify-center text-gray-400 text-sm">
|
||||||
<div class="flex flex-col items-center gap-2">
|
Aucun PDF sélectionné
|
||||||
<svg class="animate-spin h-10 w-10 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
<span class="text-white text-sm">Chargement du PDF...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Error state -->
|
|
||||||
<div v-if="error" class="absolute inset-0 flex items-center justify-center bg-gray-800/95">
|
|
||||||
<div class="text-center text-white p-4 max-w-md">
|
|
||||||
<svg class="w-12 h-12 mx-auto mb-3 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
||||||
</svg>
|
|
||||||
<p class="text-sm">{{ error }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
import { ref, watch, onUnmounted } from 'vue'
|
||||||
import * as pdfjsLib from 'pdfjs-dist'
|
|
||||||
|
|
||||||
// Configure PDF.js worker
|
|
||||||
const PDFJS_VERSION = pdfjsLib.version
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${PDFJS_VERSION}/build/pdf.worker.min.mjs`
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
file: {
|
file: {
|
||||||
@@ -123,183 +38,30 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const canvasRef = ref(null)
|
const pdfUrl = ref(null)
|
||||||
const containerRef = ref(null)
|
let objectUrl = null
|
||||||
const currentPage = ref(1)
|
|
||||||
const totalPages = ref(0)
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const error = ref(null)
|
|
||||||
const zoom = ref(1)
|
|
||||||
const baseScale = ref(1)
|
|
||||||
|
|
||||||
let pdfDoc = null
|
function cleanup() {
|
||||||
let renderTask = null
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl)
|
||||||
async function loadPdf(source) {
|
objectUrl = null
|
||||||
if (!source) return
|
|
||||||
|
|
||||||
isLoading.value = true
|
|
||||||
error.value = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
let arrayBuffer
|
|
||||||
|
|
||||||
// Support URL
|
|
||||||
if (typeof source === 'string') {
|
|
||||||
const response = await fetch(source)
|
|
||||||
if (!response.ok) throw new Error('Erreur lors du chargement du PDF')
|
|
||||||
arrayBuffer = await response.arrayBuffer()
|
|
||||||
}
|
|
||||||
// Support File
|
|
||||||
else {
|
|
||||||
arrayBuffer = await source.arrayBuffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer })
|
|
||||||
pdfDoc = await loadingTask.promise
|
|
||||||
|
|
||||||
totalPages.value = pdfDoc.numPages
|
|
||||||
currentPage.value = 1
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 50))
|
|
||||||
|
|
||||||
// Calculate base scale to fit width
|
|
||||||
await calculateBaseScale()
|
|
||||||
zoom.value = 1
|
|
||||||
|
|
||||||
await renderPage(1)
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error loading PDF:', err)
|
|
||||||
error.value = `Erreur lors du chargement: ${err.message}`
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function calculateBaseScale() {
|
watch([() => props.file, () => props.url], ([newFile, newUrl]) => {
|
||||||
if (!pdfDoc || !containerRef.value) return
|
cleanup()
|
||||||
|
|
||||||
const page = await pdfDoc.getPage(1)
|
|
||||||
const viewport = page.getViewport({ scale: 1 })
|
|
||||||
const containerWidth = containerRef.value.clientWidth - 32
|
|
||||||
baseScale.value = containerWidth / viewport.width
|
|
||||||
}
|
|
||||||
|
|
||||||
async function renderPage(pageNum) {
|
if (newUrl) {
|
||||||
if (!pdfDoc || !canvasRef.value || !containerRef.value) return
|
pdfUrl.value = newUrl
|
||||||
|
} else if (newFile) {
|
||||||
if (renderTask) {
|
objectUrl = URL.createObjectURL(newFile)
|
||||||
try { renderTask.cancel() } catch (e) {}
|
pdfUrl.value = objectUrl
|
||||||
renderTask = null
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const page = await pdfDoc.getPage(pageNum)
|
|
||||||
const canvas = canvasRef.value
|
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
|
|
||||||
const scale = baseScale.value * zoom.value
|
|
||||||
const viewport = page.getViewport({ scale })
|
|
||||||
|
|
||||||
// High DPI support
|
|
||||||
const dpr = window.devicePixelRatio || 1
|
|
||||||
canvas.width = viewport.width * dpr
|
|
||||||
canvas.height = viewport.height * dpr
|
|
||||||
canvas.style.width = viewport.width + 'px'
|
|
||||||
canvas.style.height = viewport.height + 'px'
|
|
||||||
|
|
||||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
||||||
|
|
||||||
renderTask = page.render({
|
|
||||||
canvasContext: ctx,
|
|
||||||
viewport: viewport
|
|
||||||
})
|
|
||||||
|
|
||||||
await renderTask.promise
|
|
||||||
renderTask = null
|
|
||||||
} catch (err) {
|
|
||||||
if (err.name !== 'RenderingCancelledException') {
|
|
||||||
console.error('Error rendering page:', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function prevPage() {
|
|
||||||
if (currentPage.value > 1) {
|
|
||||||
currentPage.value--
|
|
||||||
renderPage(currentPage.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextPage() {
|
|
||||||
if (currentPage.value < totalPages.value) {
|
|
||||||
currentPage.value++
|
|
||||||
renderPage(currentPage.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function zoomIn() {
|
|
||||||
if (zoom.value < 3) {
|
|
||||||
zoom.value = Math.min(3, zoom.value + 0.25)
|
|
||||||
renderPage(currentPage.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function zoomOut() {
|
|
||||||
if (zoom.value > 0.5) {
|
|
||||||
zoom.value = Math.max(0.5, zoom.value - 0.25)
|
|
||||||
renderPage(currentPage.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fitToWidth() {
|
|
||||||
zoom.value = 1
|
|
||||||
renderPage(currentPage.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWheel(e) {
|
|
||||||
if (e.deltaY < 0) {
|
|
||||||
zoomIn()
|
|
||||||
} else {
|
} else {
|
||||||
zoomOut()
|
pdfUrl.value = null
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch([() => props.file, () => props.url], async ([newFile, newUrl]) => {
|
|
||||||
const source = newUrl || newFile
|
|
||||||
if (source) {
|
|
||||||
await loadPdf(source)
|
|
||||||
} else {
|
|
||||||
pdfDoc = null
|
|
||||||
totalPages.value = 0
|
|
||||||
currentPage.value = 1
|
|
||||||
error.value = null
|
|
||||||
zoom.value = 1
|
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
let resizeTimeout = null
|
|
||||||
function handleResize() {
|
|
||||||
clearTimeout(resizeTimeout)
|
|
||||||
resizeTimeout = setTimeout(async () => {
|
|
||||||
if (pdfDoc) {
|
|
||||||
await calculateBaseScale()
|
|
||||||
renderPage(currentPage.value)
|
|
||||||
}
|
|
||||||
}, 150)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', handleResize)
|
cleanup()
|
||||||
clearTimeout(resizeTimeout)
|
|
||||||
if (renderTask) {
|
|
||||||
try { renderTask.cancel() } catch (e) {}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user