web-mojo 2.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +198 -0
- package/README.md +510 -0
- package/dist/admin.cjs.js +2 -0
- package/dist/admin.cjs.js.map +1 -0
- package/dist/admin.css +621 -0
- package/dist/admin.es.js +7973 -0
- package/dist/admin.es.js.map +1 -0
- package/dist/auth.cjs.js +2 -0
- package/dist/auth.cjs.js.map +1 -0
- package/dist/auth.css +804 -0
- package/dist/auth.es.js +2168 -0
- package/dist/auth.es.js.map +1 -0
- package/dist/charts.cjs.js +2 -0
- package/dist/charts.cjs.js.map +1 -0
- package/dist/charts.css +1002 -0
- package/dist/charts.es.js +16 -0
- package/dist/charts.es.js.map +1 -0
- package/dist/chunks/ContextMenu-BrHqj0fn.js +80 -0
- package/dist/chunks/ContextMenu-BrHqj0fn.js.map +1 -0
- package/dist/chunks/ContextMenu-gEcpSz56.js +2 -0
- package/dist/chunks/ContextMenu-gEcpSz56.js.map +1 -0
- package/dist/chunks/DataView-DPryYpEW.js +2 -0
- package/dist/chunks/DataView-DPryYpEW.js.map +1 -0
- package/dist/chunks/DataView-DjZQrpba.js +843 -0
- package/dist/chunks/DataView-DjZQrpba.js.map +1 -0
- package/dist/chunks/Dialog-BsRx4eg3.js +2 -0
- package/dist/chunks/Dialog-BsRx4eg3.js.map +1 -0
- package/dist/chunks/Dialog-DSlctbon.js +1377 -0
- package/dist/chunks/Dialog-DSlctbon.js.map +1 -0
- package/dist/chunks/FilePreviewView-BmFHzK5K.js +5868 -0
- package/dist/chunks/FilePreviewView-BmFHzK5K.js.map +1 -0
- package/dist/chunks/FilePreviewView-DcdRl_ta.js +2 -0
- package/dist/chunks/FilePreviewView-DcdRl_ta.js.map +1 -0
- package/dist/chunks/FormView-CmBuwKGD.js +2 -0
- package/dist/chunks/FormView-CmBuwKGD.js.map +1 -0
- package/dist/chunks/FormView-DqUBMPJ9.js +5054 -0
- package/dist/chunks/FormView-DqUBMPJ9.js.map +1 -0
- package/dist/chunks/MetricsChart-CM4CI6eA.js +2095 -0
- package/dist/chunks/MetricsChart-CM4CI6eA.js.map +1 -0
- package/dist/chunks/MetricsChart-CPidSMaN.js +2 -0
- package/dist/chunks/MetricsChart-CPidSMaN.js.map +1 -0
- package/dist/chunks/PDFViewer-BNQlnS83.js +2 -0
- package/dist/chunks/PDFViewer-BNQlnS83.js.map +1 -0
- package/dist/chunks/PDFViewer-Dyo-Oeyd.js +946 -0
- package/dist/chunks/PDFViewer-Dyo-Oeyd.js.map +1 -0
- package/dist/chunks/Page-B524zSQs.js +351 -0
- package/dist/chunks/Page-B524zSQs.js.map +1 -0
- package/dist/chunks/Page-BFgj0pAA.js +2 -0
- package/dist/chunks/Page-BFgj0pAA.js.map +1 -0
- package/dist/chunks/TokenManager-BXNva8Jk.js +287 -0
- package/dist/chunks/TokenManager-BXNva8Jk.js.map +1 -0
- package/dist/chunks/TokenManager-Bzn4guFm.js +2 -0
- package/dist/chunks/TokenManager-Bzn4guFm.js.map +1 -0
- package/dist/chunks/TopNav-D3I3_25f.js +371 -0
- package/dist/chunks/TopNav-D3I3_25f.js.map +1 -0
- package/dist/chunks/TopNav-MDjL4kV0.js +2 -0
- package/dist/chunks/TopNav-MDjL4kV0.js.map +1 -0
- package/dist/chunks/User-BalfYTEF.js +3 -0
- package/dist/chunks/User-BalfYTEF.js.map +1 -0
- package/dist/chunks/User-DwIT-CTQ.js +1937 -0
- package/dist/chunks/User-DwIT-CTQ.js.map +1 -0
- package/dist/chunks/WebApp-B6mgbNn2.js +4767 -0
- package/dist/chunks/WebApp-B6mgbNn2.js.map +1 -0
- package/dist/chunks/WebApp-DqDowtkl.js +2 -0
- package/dist/chunks/WebApp-DqDowtkl.js.map +1 -0
- package/dist/chunks/WebSocketClient-D6i85jl2.js +2 -0
- package/dist/chunks/WebSocketClient-D6i85jl2.js.map +1 -0
- package/dist/chunks/WebSocketClient-Dvl3AYx1.js +297 -0
- package/dist/chunks/WebSocketClient-Dvl3AYx1.js.map +1 -0
- package/dist/core.css +1181 -0
- package/dist/css/web-mojo.css +17 -0
- package/dist/css-manifest.json +6 -0
- package/dist/docit.cjs.js +2 -0
- package/dist/docit.cjs.js.map +1 -0
- package/dist/docit.es.js +959 -0
- package/dist/docit.es.js.map +1 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.es.js +2681 -0
- package/dist/index.es.js.map +1 -0
- package/dist/lightbox.cjs.js +2 -0
- package/dist/lightbox.cjs.js.map +1 -0
- package/dist/lightbox.css +606 -0
- package/dist/lightbox.es.js +3737 -0
- package/dist/lightbox.es.js.map +1 -0
- package/dist/loader.es.js +115 -0
- package/dist/loader.umd.js +85 -0
- package/dist/portal.css +2446 -0
- package/dist/table.css +639 -0
- package/dist/toast.css +181 -0
- package/package.json +179 -0
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
import { V as View } from "./WebApp-B6mgbNn2.js";
|
|
2
|
+
import Dialog from "./Dialog-DSlctbon.js";
|
|
3
|
+
class LightboxGallery extends View {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
super({
|
|
6
|
+
...options,
|
|
7
|
+
className: `lightbox-gallery ${options.className || ""}`,
|
|
8
|
+
tagName: "div"
|
|
9
|
+
});
|
|
10
|
+
const rawImages = Array.isArray(options.images) ? options.images : [options.images || options.src].filter(Boolean);
|
|
11
|
+
this.images = rawImages.map((img) => {
|
|
12
|
+
if (typeof img === "string") {
|
|
13
|
+
return { src: img, alt: "" };
|
|
14
|
+
}
|
|
15
|
+
return { src: img.src, alt: img.alt || "" };
|
|
16
|
+
});
|
|
17
|
+
this.currentIndex = options.startIndex || 0;
|
|
18
|
+
this.showNavigation = options.showNavigation !== false && this.images.length > 1;
|
|
19
|
+
this.showCounter = options.showCounter !== false && this.images.length > 1;
|
|
20
|
+
this.allowKeyboard = options.allowKeyboard !== false;
|
|
21
|
+
this.closeOnBackdrop = options.closeOnBackdrop !== false;
|
|
22
|
+
this.fitToScreen = options.fitToScreen !== false;
|
|
23
|
+
this._keyboardHandler = this.handleKeyboard.bind(this);
|
|
24
|
+
this.updateTemplateProperties();
|
|
25
|
+
}
|
|
26
|
+
updateTemplateProperties() {
|
|
27
|
+
this.currentImage = this.images[this.currentIndex] || { src: "", alt: "" };
|
|
28
|
+
this.currentNumber = this.currentIndex + 1;
|
|
29
|
+
this.total = this.images.length;
|
|
30
|
+
this.isFirst = this.currentIndex === 0;
|
|
31
|
+
this.isLast = this.currentIndex === this.images.length - 1;
|
|
32
|
+
this.imageStyle = this.fitToScreen ? "width: 90vw; max-height: 100%; object-fit: contain; user-select: none; cursor: zoom-in;" : "max-width: none; max-height: none; user-select: none; cursor: zoom-out;";
|
|
33
|
+
this.containerStyle = this.fitToScreen ? "" : "overflow: auto;";
|
|
34
|
+
this.modeIndicator = this.fitToScreen ? "Fit to Screen" : "Original Size";
|
|
35
|
+
}
|
|
36
|
+
async getTemplate() {
|
|
37
|
+
this.images[this.currentIndex];
|
|
38
|
+
this.images.length > 1;
|
|
39
|
+
return `
|
|
40
|
+
<div class="lightbox-overlay position-fixed top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
|
|
41
|
+
style="background: rgba(0,0,0,0.9); z-index: 9999;"
|
|
42
|
+
data-action="backdrop-click">
|
|
43
|
+
|
|
44
|
+
<!-- Close button -->
|
|
45
|
+
<button type="button" class="btn-close btn-close-white position-absolute top-0 end-0 m-4"
|
|
46
|
+
data-action="close"
|
|
47
|
+
style="z-index: 10001;"
|
|
48
|
+
title="Close"></button>
|
|
49
|
+
|
|
50
|
+
<!-- Counter -->
|
|
51
|
+
{{#showCounter}}
|
|
52
|
+
<div class="lightbox-counter position-absolute top-0 start-50 translate-middle-x mt-4 text-white"
|
|
53
|
+
style="z-index: 10001; font-size: 1.1rem;">
|
|
54
|
+
{{currentNumber}} of {{total}}
|
|
55
|
+
</div>
|
|
56
|
+
{{/showCounter}}
|
|
57
|
+
|
|
58
|
+
<!-- Mode Indicator -->
|
|
59
|
+
<div class="lightbox-mode-indicator position-absolute bottom-0 start-50 translate-middle-x mb-4 text-white bg-dark bg-opacity-75 px-3 py-2 rounded"
|
|
60
|
+
style="z-index: 10001; font-size: 0.9rem;">
|
|
61
|
+
{{modeIndicator}} • Click image to toggle
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Navigation -->
|
|
65
|
+
{{#showNavigation}}
|
|
66
|
+
<button type="button" class="btn btn-light btn-lg position-absolute start-0 top-50 translate-middle-y ms-4"
|
|
67
|
+
data-action="prev"
|
|
68
|
+
style="z-index: 10001;"
|
|
69
|
+
title="Previous"
|
|
70
|
+
{{#isFirst}}disabled{{/isFirst}}>
|
|
71
|
+
<i class="bi bi-chevron-left"></i>
|
|
72
|
+
</button>
|
|
73
|
+
|
|
74
|
+
<button type="button" class="btn btn-light btn-lg position-absolute end-0 top-50 translate-middle-y me-4"
|
|
75
|
+
data-action="next"
|
|
76
|
+
style="z-index: 10001;"
|
|
77
|
+
title="Next"
|
|
78
|
+
{{#isLast}}disabled{{/isLast}}>
|
|
79
|
+
<i class="bi bi-chevron-right"></i>
|
|
80
|
+
</button>
|
|
81
|
+
{{/showNavigation}}
|
|
82
|
+
|
|
83
|
+
<!-- Image container -->
|
|
84
|
+
<div class="lightbox-image-container w-100 h-100 d-flex align-items-center justify-content-center p-5"
|
|
85
|
+
style="{{containerStyle}}">
|
|
86
|
+
{{#currentImage}}
|
|
87
|
+
<img src="{{src}}"
|
|
88
|
+
alt="{{alt}}"
|
|
89
|
+
class="lightbox-image img-fluid"
|
|
90
|
+
style="{{imageStyle}}"
|
|
91
|
+
data-action="image-click">
|
|
92
|
+
{{/currentImage}}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Loading spinner -->
|
|
96
|
+
<div class="lightbox-loading position-absolute top-50 start-50 translate-middle text-white"
|
|
97
|
+
style="display: none;">
|
|
98
|
+
<div class="spinner-border" role="status">
|
|
99
|
+
<span class="visually-hidden">Loading...</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
async onAfterRender() {
|
|
106
|
+
document.body.appendChild(this.element);
|
|
107
|
+
document.body.style.overflow = "hidden";
|
|
108
|
+
if (this.allowKeyboard) {
|
|
109
|
+
document.addEventListener("keydown", this._keyboardHandler);
|
|
110
|
+
}
|
|
111
|
+
this.preloadAdjacentImages();
|
|
112
|
+
}
|
|
113
|
+
// Action handlers
|
|
114
|
+
async handleActionClose() {
|
|
115
|
+
this.close();
|
|
116
|
+
}
|
|
117
|
+
async handleActionBackdropClick(e) {
|
|
118
|
+
if (this.closeOnBackdrop && e.target === e.currentTarget) {
|
|
119
|
+
this.close();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async handleActionPrev() {
|
|
123
|
+
this.showPrevious();
|
|
124
|
+
}
|
|
125
|
+
async handleActionNext() {
|
|
126
|
+
this.showNext();
|
|
127
|
+
}
|
|
128
|
+
async handleActionImageClick() {
|
|
129
|
+
this.toggleImageMode();
|
|
130
|
+
}
|
|
131
|
+
// Navigation methods
|
|
132
|
+
showPrevious() {
|
|
133
|
+
if (this.currentIndex > 0) {
|
|
134
|
+
this.currentIndex--;
|
|
135
|
+
this.updateImage();
|
|
136
|
+
this.preloadAdjacentImages();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
showNext() {
|
|
140
|
+
if (this.currentIndex < this.images.length - 1) {
|
|
141
|
+
this.currentIndex++;
|
|
142
|
+
this.updateImage();
|
|
143
|
+
this.preloadAdjacentImages();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
goToImage(index) {
|
|
147
|
+
if (index >= 0 && index < this.images.length && index !== this.currentIndex) {
|
|
148
|
+
this.currentIndex = index;
|
|
149
|
+
this.updateImage();
|
|
150
|
+
this.preloadAdjacentImages();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Update image without full re-render
|
|
154
|
+
async updateImage() {
|
|
155
|
+
const currentImage = this.images[this.currentIndex];
|
|
156
|
+
const imgElement = this.element.querySelector(".lightbox-image");
|
|
157
|
+
const counterElement = this.element.querySelector(".lightbox-counter");
|
|
158
|
+
const prevBtn = this.element.querySelector('[data-action="prev"]');
|
|
159
|
+
const nextBtn = this.element.querySelector('[data-action="next"]');
|
|
160
|
+
if (imgElement) {
|
|
161
|
+
this.showLoading();
|
|
162
|
+
imgElement.src = currentImage.src;
|
|
163
|
+
imgElement.alt = currentImage.alt;
|
|
164
|
+
await this.waitForImageLoad(imgElement);
|
|
165
|
+
this.hideLoading();
|
|
166
|
+
}
|
|
167
|
+
if (counterElement) {
|
|
168
|
+
counterElement.textContent = `${this.currentIndex + 1} of ${this.images.length}`;
|
|
169
|
+
}
|
|
170
|
+
if (prevBtn) {
|
|
171
|
+
prevBtn.disabled = this.currentIndex === 0;
|
|
172
|
+
}
|
|
173
|
+
if (nextBtn) {
|
|
174
|
+
nextBtn.disabled = this.currentIndex === this.images.length - 1;
|
|
175
|
+
}
|
|
176
|
+
this.updateTemplateProperties();
|
|
177
|
+
this.updateImageDisplay();
|
|
178
|
+
const eventBus = this.getApp()?.events;
|
|
179
|
+
if (eventBus) {
|
|
180
|
+
eventBus.emit("lightbox:image-changed", {
|
|
181
|
+
gallery: this,
|
|
182
|
+
index: this.currentIndex,
|
|
183
|
+
image: currentImage
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
showLoading() {
|
|
188
|
+
const loading = this.element.querySelector(".lightbox-loading");
|
|
189
|
+
if (loading) {
|
|
190
|
+
loading.style.display = "block";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
hideLoading() {
|
|
194
|
+
const loading = this.element.querySelector(".lightbox-loading");
|
|
195
|
+
if (loading) {
|
|
196
|
+
loading.style.display = "none";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
waitForImageLoad(imgElement) {
|
|
200
|
+
return new Promise((resolve) => {
|
|
201
|
+
if (imgElement.complete) {
|
|
202
|
+
resolve();
|
|
203
|
+
} else {
|
|
204
|
+
imgElement.onload = resolve;
|
|
205
|
+
imgElement.onerror = resolve;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// Preload adjacent images for smooth navigation
|
|
210
|
+
preloadAdjacentImages() {
|
|
211
|
+
const preloadIndexes = [];
|
|
212
|
+
if (this.currentIndex > 0) {
|
|
213
|
+
preloadIndexes.push(this.currentIndex - 1);
|
|
214
|
+
}
|
|
215
|
+
if (this.currentIndex < this.images.length - 1) {
|
|
216
|
+
preloadIndexes.push(this.currentIndex + 1);
|
|
217
|
+
}
|
|
218
|
+
preloadIndexes.forEach((index) => {
|
|
219
|
+
const image = this.images[index];
|
|
220
|
+
const preloadImg = new Image();
|
|
221
|
+
preloadImg.src = image.src;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// Keyboard navigation
|
|
225
|
+
handleKeyboard(e) {
|
|
226
|
+
switch (e.key) {
|
|
227
|
+
case "Escape":
|
|
228
|
+
e.preventDefault();
|
|
229
|
+
this.close();
|
|
230
|
+
break;
|
|
231
|
+
case "ArrowLeft":
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
this.showPrevious();
|
|
234
|
+
break;
|
|
235
|
+
case "ArrowRight":
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
this.showNext();
|
|
238
|
+
break;
|
|
239
|
+
case "Home":
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
this.goToImage(0);
|
|
242
|
+
break;
|
|
243
|
+
case "End":
|
|
244
|
+
e.preventDefault();
|
|
245
|
+
this.goToImage(this.images.length - 1);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Toggle between fit-to-screen and original size
|
|
250
|
+
toggleImageMode() {
|
|
251
|
+
this.fitToScreen = !this.fitToScreen;
|
|
252
|
+
this.updateTemplateProperties();
|
|
253
|
+
this.updateImageDisplay();
|
|
254
|
+
const eventBus = this.getApp()?.events;
|
|
255
|
+
if (eventBus) {
|
|
256
|
+
eventBus.emit("lightbox:mode-changed", {
|
|
257
|
+
gallery: this,
|
|
258
|
+
fitToScreen: this.fitToScreen
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Update image display without full re-render
|
|
263
|
+
updateImageDisplay() {
|
|
264
|
+
const imgElement = this.element.querySelector(".lightbox-image");
|
|
265
|
+
const containerElement = this.element.querySelector(".lightbox-image-container");
|
|
266
|
+
const indicatorElement = this.element.querySelector(".lightbox-mode-indicator");
|
|
267
|
+
if (imgElement) {
|
|
268
|
+
if (this.fitToScreen) {
|
|
269
|
+
imgElement.style.maxWidth = "100%";
|
|
270
|
+
imgElement.style.maxHeight = "100%";
|
|
271
|
+
imgElement.style.objectFit = "contain";
|
|
272
|
+
imgElement.style.cursor = "zoom-in";
|
|
273
|
+
} else {
|
|
274
|
+
imgElement.style.maxWidth = "none";
|
|
275
|
+
imgElement.style.maxHeight = "none";
|
|
276
|
+
imgElement.style.objectFit = "none";
|
|
277
|
+
imgElement.style.cursor = "zoom-out";
|
|
278
|
+
}
|
|
279
|
+
imgElement.style.userSelect = "none";
|
|
280
|
+
}
|
|
281
|
+
if (containerElement) {
|
|
282
|
+
containerElement.style.overflow = this.fitToScreen ? "" : "auto";
|
|
283
|
+
}
|
|
284
|
+
if (indicatorElement) {
|
|
285
|
+
indicatorElement.textContent = `${this.modeIndicator} • Click image to toggle`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Close lightbox
|
|
289
|
+
close() {
|
|
290
|
+
const eventBus = this.getApp()?.events;
|
|
291
|
+
if (eventBus) {
|
|
292
|
+
eventBus.emit("lightbox:closed", { gallery: this });
|
|
293
|
+
}
|
|
294
|
+
this.destroy();
|
|
295
|
+
}
|
|
296
|
+
async onBeforeDestroy() {
|
|
297
|
+
document.body.style.overflow = "";
|
|
298
|
+
if (this.allowKeyboard) {
|
|
299
|
+
document.removeEventListener("keydown", this._keyboardHandler);
|
|
300
|
+
}
|
|
301
|
+
if (this.element.parentNode === document.body) {
|
|
302
|
+
document.body.removeChild(this.element);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Static method to show lightbox
|
|
306
|
+
static show(images, options = {}) {
|
|
307
|
+
const lightbox = new LightboxGallery({
|
|
308
|
+
images,
|
|
309
|
+
...options
|
|
310
|
+
});
|
|
311
|
+
lightbox.render().then(() => {
|
|
312
|
+
lightbox.mount();
|
|
313
|
+
});
|
|
314
|
+
return lightbox;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
window.LightboxGallery = LightboxGallery;
|
|
318
|
+
class PDFViewer extends View {
|
|
319
|
+
constructor(options = {}) {
|
|
320
|
+
super({
|
|
321
|
+
...options,
|
|
322
|
+
className: `pdf-viewer ${options.className || ""}`,
|
|
323
|
+
tagName: "div"
|
|
324
|
+
});
|
|
325
|
+
this.pdfUrl = options.pdfUrl || options.src || "";
|
|
326
|
+
this.title = options.title || "PDF Document";
|
|
327
|
+
this.pdfDoc = null;
|
|
328
|
+
this.currentPage = 1;
|
|
329
|
+
this.totalPages = 0;
|
|
330
|
+
this.pageRendering = false;
|
|
331
|
+
this.pageNumPending = null;
|
|
332
|
+
this.scale = 1;
|
|
333
|
+
this.minScale = 0.25;
|
|
334
|
+
this.maxScale = 5;
|
|
335
|
+
this.scaleStep = 0.25;
|
|
336
|
+
this.fitMode = "page";
|
|
337
|
+
this.canvas = null;
|
|
338
|
+
this.ctx = null;
|
|
339
|
+
this.showControls = options.showControls !== false;
|
|
340
|
+
this.allowZoom = options.allowZoom !== false;
|
|
341
|
+
this.allowNavigation = options.allowNavigation !== false;
|
|
342
|
+
this.showPageNumbers = options.showPageNumbers !== false;
|
|
343
|
+
this.isLoaded = false;
|
|
344
|
+
this.isLoading = false;
|
|
345
|
+
this.pdfjsWorkerPath = options.pdfjsWorkerPath || "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.js";
|
|
346
|
+
this.pdfjsCMapUrl = options.pdfjsCMapUrl || "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/cmaps/";
|
|
347
|
+
this.canvasContainer = null;
|
|
348
|
+
this.controlsElement = null;
|
|
349
|
+
this.statusElement = null;
|
|
350
|
+
this.pageInput = null;
|
|
351
|
+
}
|
|
352
|
+
async getTemplate() {
|
|
353
|
+
return `
|
|
354
|
+
<div class="pdf-viewer-container">
|
|
355
|
+
{{#showControls}}
|
|
356
|
+
<div class="pdf-viewer-toolbar" data-container="toolbar">
|
|
357
|
+
<div class="btn-toolbar" role="toolbar">
|
|
358
|
+
<!-- Navigation Controls -->
|
|
359
|
+
{{#allowNavigation}}
|
|
360
|
+
<div class="btn-group me-2" role="group" aria-label="Navigation">
|
|
361
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="first-page" title="First Page">
|
|
362
|
+
<i class="bi bi-chevron-double-left"></i>
|
|
363
|
+
</button>
|
|
364
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="prev-page" title="Previous Page">
|
|
365
|
+
<i class="bi bi-chevron-left"></i>
|
|
366
|
+
</button>
|
|
367
|
+
|
|
368
|
+
{{#showPageNumbers}}
|
|
369
|
+
<div class="input-group input-group-sm" style="width: 120px;">
|
|
370
|
+
<input type="number" class="form-control text-center page-input" min="1" value="1" data-change-action="page-input">
|
|
371
|
+
<span class="input-group-text page-total">/ 0</span>
|
|
372
|
+
</div>
|
|
373
|
+
{{/showPageNumbers}}
|
|
374
|
+
|
|
375
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="next-page" title="Next Page">
|
|
376
|
+
<i class="bi bi-chevron-right"></i>
|
|
377
|
+
</button>
|
|
378
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="last-page" title="Last Page">
|
|
379
|
+
<i class="bi bi-chevron-double-right"></i>
|
|
380
|
+
</button>
|
|
381
|
+
</div>
|
|
382
|
+
{{/allowNavigation}}
|
|
383
|
+
|
|
384
|
+
<!-- Zoom Controls -->
|
|
385
|
+
{{#allowZoom}}
|
|
386
|
+
<div class="btn-group me-2" role="group" aria-label="Zoom">
|
|
387
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="zoom-out" title="Zoom Out">
|
|
388
|
+
<i class="bi bi-zoom-out"></i>
|
|
389
|
+
</button>
|
|
390
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="zoom-in" title="Zoom In">
|
|
391
|
+
<i class="bi bi-zoom-in"></i>
|
|
392
|
+
</button>
|
|
393
|
+
|
|
394
|
+
<div class="btn-group" role="group">
|
|
395
|
+
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" title="Fit">
|
|
396
|
+
<i class="bi bi-arrows-fullscreen"></i>
|
|
397
|
+
</button>
|
|
398
|
+
<ul class="dropdown-menu">
|
|
399
|
+
<li><a class="dropdown-item" href="#" data-action="fit-page">Fit Page</a></li>
|
|
400
|
+
<li><a class="dropdown-item" href="#" data-action="fit-width">Fit Width</a></li>
|
|
401
|
+
<li><a class="dropdown-item" href="#" data-action="actual-size">Actual Size</a></li>
|
|
402
|
+
</ul>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
{{/allowZoom}}
|
|
406
|
+
|
|
407
|
+
<!-- Utility Controls -->
|
|
408
|
+
<div class="btn-group me-2" role="group" aria-label="Utilities">
|
|
409
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="download" title="Download">
|
|
410
|
+
<i class="bi bi-download"></i>
|
|
411
|
+
</button>
|
|
412
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="print" title="Print">
|
|
413
|
+
<i class="bi bi-printer"></i>
|
|
414
|
+
</button>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
{{/showControls}}
|
|
419
|
+
|
|
420
|
+
<!-- PDF Content Area -->
|
|
421
|
+
<div class="pdf-viewer-content" data-container="content">
|
|
422
|
+
<div class="pdf-canvas-container" data-container="canvasContainer">
|
|
423
|
+
<canvas class="pdf-canvas" data-container="canvas"></canvas>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
<div class="pdf-viewer-overlay">
|
|
427
|
+
<div class="pdf-viewer-loading">
|
|
428
|
+
<div class="spinner-border text-primary" role="status">
|
|
429
|
+
<span class="visually-hidden">Loading PDF...</span>
|
|
430
|
+
</div>
|
|
431
|
+
<div class="mt-2">Loading PDF...</div>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
<div class="pdf-viewer-error" style="display: none;">
|
|
436
|
+
<div class="alert alert-danger" role="alert">
|
|
437
|
+
<i class="bi bi-exclamation-triangle"></i>
|
|
438
|
+
<strong>Error:</strong> <span class="error-message">Failed to load PDF</span>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<!-- Status Bar -->
|
|
444
|
+
<div class="pdf-viewer-status" data-container="status">
|
|
445
|
+
<small class="text-muted">
|
|
446
|
+
<span class="current-page">0</span> of <span class="total-pages">0</span> pages |
|
|
447
|
+
<span class="zoom-level">100%</span> |
|
|
448
|
+
<span class="document-title">{{title}}</span>
|
|
449
|
+
</small>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
`;
|
|
453
|
+
}
|
|
454
|
+
get() {
|
|
455
|
+
return {
|
|
456
|
+
pdfUrl: this.pdfUrl,
|
|
457
|
+
title: this.title,
|
|
458
|
+
showControls: this.showControls,
|
|
459
|
+
allowZoom: this.allowZoom,
|
|
460
|
+
allowNavigation: this.allowNavigation,
|
|
461
|
+
showPageNumbers: this.showPageNumbers
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
async onAfterRender() {
|
|
465
|
+
this.canvas = this.element.querySelector(".pdf-canvas");
|
|
466
|
+
this.canvasContainer = this.element.querySelector(".pdf-canvas-container");
|
|
467
|
+
this.controlsElement = this.element.querySelector(".pdf-viewer-toolbar");
|
|
468
|
+
this.statusElement = this.element.querySelector(".pdf-viewer-status");
|
|
469
|
+
this.pageInput = this.element.querySelector(".page-input");
|
|
470
|
+
this.overlayElement = this.element.querySelector(".pdf-viewer-overlay");
|
|
471
|
+
this.errorElement = this.element.querySelector(".pdf-viewer-error");
|
|
472
|
+
if (this.canvas) {
|
|
473
|
+
this.ctx = this.canvas.getContext("2d");
|
|
474
|
+
}
|
|
475
|
+
this.setupEssentialEventListeners();
|
|
476
|
+
await this.initializePDFJS();
|
|
477
|
+
if (this.pdfUrl) {
|
|
478
|
+
await this.loadPDF();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
setupEssentialEventListeners() {
|
|
482
|
+
const keydownHandler = (e) => this.handleKeyDown(e);
|
|
483
|
+
document.addEventListener("keydown", keydownHandler);
|
|
484
|
+
let resizeObserver = null;
|
|
485
|
+
if (this.canvasContainer) {
|
|
486
|
+
resizeObserver = new ResizeObserver(() => {
|
|
487
|
+
if (this.fitMode !== "auto") {
|
|
488
|
+
this.applyFitMode();
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
resizeObserver.observe(this.canvasContainer);
|
|
492
|
+
}
|
|
493
|
+
this._essentialListeners = [
|
|
494
|
+
{ el: document, type: "keydown", fn: keydownHandler }
|
|
495
|
+
];
|
|
496
|
+
this._resizeObserver = resizeObserver;
|
|
497
|
+
}
|
|
498
|
+
async initializePDFJS() {
|
|
499
|
+
try {
|
|
500
|
+
if (typeof window.pdfjsLib === "undefined") {
|
|
501
|
+
await this.loadPDFJSLibrary();
|
|
502
|
+
}
|
|
503
|
+
window.pdfjsLib.GlobalWorkerOptions.workerSrc = this.pdfjsWorkerPath;
|
|
504
|
+
return true;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error("Failed to initialize PDF.js:", error);
|
|
507
|
+
this.showError("Failed to initialize PDF viewer");
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async loadPDFJSLibrary() {
|
|
512
|
+
return new Promise((resolve, reject) => {
|
|
513
|
+
const script = document.createElement("script");
|
|
514
|
+
script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.min.js";
|
|
515
|
+
script.onload = resolve;
|
|
516
|
+
script.onerror = reject;
|
|
517
|
+
document.head.appendChild(script);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
// Action Handlers
|
|
521
|
+
async handleActionFirstPage() {
|
|
522
|
+
await this.goToPage(1);
|
|
523
|
+
}
|
|
524
|
+
async handleActionPrevPage() {
|
|
525
|
+
await this.goToPage(this.currentPage - 1);
|
|
526
|
+
}
|
|
527
|
+
async handleActionNextPage() {
|
|
528
|
+
await this.goToPage(this.currentPage + 1);
|
|
529
|
+
}
|
|
530
|
+
async handleActionLastPage() {
|
|
531
|
+
await this.goToPage(this.totalPages);
|
|
532
|
+
}
|
|
533
|
+
async onChangePageInput(event, element) {
|
|
534
|
+
const pageNumber = parseInt(element.value, 10);
|
|
535
|
+
if (pageNumber >= 1 && pageNumber <= this.totalPages) {
|
|
536
|
+
await this.goToPage(pageNumber);
|
|
537
|
+
} else {
|
|
538
|
+
element.value = this.currentPage;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async handleActionZoomIn() {
|
|
542
|
+
this.setScale(this.scale + this.scaleStep);
|
|
543
|
+
}
|
|
544
|
+
async handleActionZoomOut() {
|
|
545
|
+
this.setScale(this.scale - this.scaleStep);
|
|
546
|
+
}
|
|
547
|
+
async handleActionFitPage() {
|
|
548
|
+
this.setFitMode("page");
|
|
549
|
+
}
|
|
550
|
+
async handleActionFitWidth() {
|
|
551
|
+
this.setFitMode("width");
|
|
552
|
+
}
|
|
553
|
+
async handleActionActualSize() {
|
|
554
|
+
this.setScale(1);
|
|
555
|
+
this.fitMode = "auto";
|
|
556
|
+
}
|
|
557
|
+
async handleActionDownload() {
|
|
558
|
+
this.downloadPDF();
|
|
559
|
+
}
|
|
560
|
+
async handleActionPrint() {
|
|
561
|
+
window.print();
|
|
562
|
+
}
|
|
563
|
+
// PDF Loading
|
|
564
|
+
async loadPDF() {
|
|
565
|
+
if (!this.pdfUrl || !window.pdfjsLib) {
|
|
566
|
+
this.showError("PDF URL or PDF.js library not available");
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
this.isLoading = true;
|
|
570
|
+
this.showLoading();
|
|
571
|
+
try {
|
|
572
|
+
const loadingTask = window.pdfjsLib.getDocument({
|
|
573
|
+
url: this.pdfUrl,
|
|
574
|
+
cMapUrl: this.pdfjsCMapUrl,
|
|
575
|
+
cMapPacked: true
|
|
576
|
+
});
|
|
577
|
+
this.pdfDoc = await loadingTask.promise;
|
|
578
|
+
this.totalPages = this.pdfDoc.numPages;
|
|
579
|
+
this.currentPage = 1;
|
|
580
|
+
this.updatePageControls();
|
|
581
|
+
this.updateStatus();
|
|
582
|
+
await this.renderPage(1);
|
|
583
|
+
this.isLoaded = true;
|
|
584
|
+
this.isLoading = false;
|
|
585
|
+
this.element.classList.add("loaded");
|
|
586
|
+
this.hideLoading();
|
|
587
|
+
this.applyFitMode();
|
|
588
|
+
const eventBus = this.getApp()?.events;
|
|
589
|
+
if (eventBus) {
|
|
590
|
+
eventBus.emit("pdfviewer:loaded", {
|
|
591
|
+
viewer: this,
|
|
592
|
+
pdfUrl: this.pdfUrl,
|
|
593
|
+
totalPages: this.totalPages
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
return true;
|
|
597
|
+
} catch (error) {
|
|
598
|
+
console.error("Error loading PDF:", error);
|
|
599
|
+
this.isLoading = false;
|
|
600
|
+
this.showError("Failed to load PDF document");
|
|
601
|
+
const eventBus = this.getApp()?.events;
|
|
602
|
+
if (eventBus) {
|
|
603
|
+
eventBus.emit("pdfviewer:error", {
|
|
604
|
+
viewer: this,
|
|
605
|
+
pdfUrl: this.pdfUrl,
|
|
606
|
+
error: error.message
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async renderPage(pageNumber) {
|
|
613
|
+
if (this.pageRendering) {
|
|
614
|
+
this.pageNumPending = pageNumber;
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (!this.pdfDoc || !this.canvas || !this.ctx) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
this.pageRendering = true;
|
|
621
|
+
this.currentPage = pageNumber;
|
|
622
|
+
try {
|
|
623
|
+
const page = await this.pdfDoc.getPage(pageNumber);
|
|
624
|
+
const viewport = page.getViewport({ scale: this.scale });
|
|
625
|
+
this.canvas.height = viewport.height;
|
|
626
|
+
this.canvas.width = viewport.width;
|
|
627
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
628
|
+
const renderContext = {
|
|
629
|
+
canvasContext: this.ctx,
|
|
630
|
+
viewport
|
|
631
|
+
};
|
|
632
|
+
const renderTask = page.render(renderContext);
|
|
633
|
+
await renderTask.promise;
|
|
634
|
+
this.pageRendering = false;
|
|
635
|
+
if (this.pageNumPending !== null) {
|
|
636
|
+
const pendingPage = this.pageNumPending;
|
|
637
|
+
this.pageNumPending = null;
|
|
638
|
+
await this.renderPage(pendingPage);
|
|
639
|
+
}
|
|
640
|
+
this.updatePageControls();
|
|
641
|
+
this.updateStatus();
|
|
642
|
+
const eventBus = this.getApp()?.events;
|
|
643
|
+
if (eventBus) {
|
|
644
|
+
eventBus.emit("pdfviewer:page-changed", {
|
|
645
|
+
viewer: this,
|
|
646
|
+
currentPage: this.currentPage,
|
|
647
|
+
totalPages: this.totalPages
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
} catch (error) {
|
|
651
|
+
console.error("Error rendering page:", error);
|
|
652
|
+
this.pageRendering = false;
|
|
653
|
+
this.showError("Failed to render PDF page");
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// Navigation
|
|
657
|
+
async goToPage(pageNumber) {
|
|
658
|
+
if (!this.pdfDoc || pageNumber < 1 || pageNumber > this.totalPages) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
await this.renderPage(pageNumber);
|
|
662
|
+
}
|
|
663
|
+
// Zoom and Fit
|
|
664
|
+
setScale(scale) {
|
|
665
|
+
const oldScale = this.scale;
|
|
666
|
+
this.scale = Math.max(this.minScale, Math.min(this.maxScale, scale));
|
|
667
|
+
this.fitMode = "auto";
|
|
668
|
+
if (this.isLoaded) {
|
|
669
|
+
this.renderPage(this.currentPage);
|
|
670
|
+
}
|
|
671
|
+
const eventBus = this.getApp()?.events;
|
|
672
|
+
if (eventBus && oldScale !== this.scale) {
|
|
673
|
+
eventBus.emit("pdfviewer:scale-changed", {
|
|
674
|
+
viewer: this,
|
|
675
|
+
oldScale,
|
|
676
|
+
newScale: this.scale
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
setFitMode(mode) {
|
|
681
|
+
const oldMode = this.fitMode;
|
|
682
|
+
this.fitMode = mode;
|
|
683
|
+
this.applyFitMode();
|
|
684
|
+
const eventBus = this.getApp()?.events;
|
|
685
|
+
if (eventBus) {
|
|
686
|
+
eventBus.emit("pdfviewer:fit-mode-changed", {
|
|
687
|
+
viewer: this,
|
|
688
|
+
oldMode,
|
|
689
|
+
newMode: mode
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
applyFitMode() {
|
|
694
|
+
if (!this.isLoaded || !this.pdfDoc || !this.canvasContainer) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
this.pdfDoc.getPage(this.currentPage).then((page) => {
|
|
698
|
+
const containerRect = this.canvasContainer.getBoundingClientRect();
|
|
699
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
700
|
+
let newScale;
|
|
701
|
+
if (this.fitMode === "page") {
|
|
702
|
+
const scaleX = (containerRect.width - 40) / viewport.width;
|
|
703
|
+
const scaleY = (containerRect.height - 40) / viewport.height;
|
|
704
|
+
newScale = Math.min(scaleX, scaleY);
|
|
705
|
+
} else if (this.fitMode === "width") {
|
|
706
|
+
newScale = (containerRect.width - 40) / viewport.width;
|
|
707
|
+
} else {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
this.scale = Math.max(this.minScale, Math.min(this.maxScale, newScale));
|
|
711
|
+
this.renderPage(this.currentPage);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
// Event Handlers
|
|
715
|
+
handleKeyDown(e) {
|
|
716
|
+
if (e.target.tagName === "INPUT" && e.target !== this.pageInput) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
switch (e.key) {
|
|
720
|
+
case "ArrowLeft":
|
|
721
|
+
case "PageUp":
|
|
722
|
+
e.preventDefault();
|
|
723
|
+
this.goToPage(this.currentPage - 1);
|
|
724
|
+
break;
|
|
725
|
+
case "ArrowRight":
|
|
726
|
+
case "PageDown":
|
|
727
|
+
e.preventDefault();
|
|
728
|
+
this.goToPage(this.currentPage + 1);
|
|
729
|
+
break;
|
|
730
|
+
case "Home":
|
|
731
|
+
e.preventDefault();
|
|
732
|
+
this.goToPage(1);
|
|
733
|
+
break;
|
|
734
|
+
case "End":
|
|
735
|
+
e.preventDefault();
|
|
736
|
+
this.goToPage(this.totalPages);
|
|
737
|
+
break;
|
|
738
|
+
case "+":
|
|
739
|
+
case "=":
|
|
740
|
+
if (e.ctrlKey || e.metaKey) {
|
|
741
|
+
e.preventDefault();
|
|
742
|
+
this.setScale(this.scale + this.scaleStep);
|
|
743
|
+
}
|
|
744
|
+
break;
|
|
745
|
+
case "-":
|
|
746
|
+
if (e.ctrlKey || e.metaKey) {
|
|
747
|
+
e.preventDefault();
|
|
748
|
+
this.setScale(this.scale - this.scaleStep);
|
|
749
|
+
}
|
|
750
|
+
break;
|
|
751
|
+
case "0":
|
|
752
|
+
if (e.ctrlKey || e.metaKey) {
|
|
753
|
+
e.preventDefault();
|
|
754
|
+
this.setFitMode("page");
|
|
755
|
+
}
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// UI Updates
|
|
760
|
+
updatePageControls() {
|
|
761
|
+
if (this.pageInput) {
|
|
762
|
+
this.pageInput.value = this.currentPage;
|
|
763
|
+
}
|
|
764
|
+
const pageTotalElement = this.element.querySelector(".page-total");
|
|
765
|
+
if (pageTotalElement) {
|
|
766
|
+
pageTotalElement.textContent = `/ ${this.totalPages}`;
|
|
767
|
+
}
|
|
768
|
+
const firstBtn = this.element.querySelector('[data-action="first-page"]');
|
|
769
|
+
const prevBtn = this.element.querySelector('[data-action="prev-page"]');
|
|
770
|
+
const nextBtn = this.element.querySelector('[data-action="next-page"]');
|
|
771
|
+
const lastBtn = this.element.querySelector('[data-action="last-page"]');
|
|
772
|
+
if (firstBtn) firstBtn.disabled = this.currentPage <= 1;
|
|
773
|
+
if (prevBtn) prevBtn.disabled = this.currentPage <= 1;
|
|
774
|
+
if (nextBtn) nextBtn.disabled = this.currentPage >= this.totalPages;
|
|
775
|
+
if (lastBtn) lastBtn.disabled = this.currentPage >= this.totalPages;
|
|
776
|
+
const zoomInBtn = this.element.querySelector('[data-action="zoom-in"]');
|
|
777
|
+
const zoomOutBtn = this.element.querySelector('[data-action="zoom-out"]');
|
|
778
|
+
if (zoomInBtn) zoomInBtn.disabled = this.scale >= this.maxScale;
|
|
779
|
+
if (zoomOutBtn) zoomOutBtn.disabled = this.scale <= this.minScale;
|
|
780
|
+
}
|
|
781
|
+
updateStatus() {
|
|
782
|
+
if (!this.statusElement) return;
|
|
783
|
+
const currentPageElement = this.statusElement.querySelector(".current-page");
|
|
784
|
+
const totalPagesElement = this.statusElement.querySelector(".total-pages");
|
|
785
|
+
const zoomLevelElement = this.statusElement.querySelector(".zoom-level");
|
|
786
|
+
if (currentPageElement) {
|
|
787
|
+
currentPageElement.textContent = this.currentPage;
|
|
788
|
+
}
|
|
789
|
+
if (totalPagesElement) {
|
|
790
|
+
totalPagesElement.textContent = this.totalPages;
|
|
791
|
+
}
|
|
792
|
+
if (zoomLevelElement) {
|
|
793
|
+
zoomLevelElement.textContent = `${Math.round(this.scale * 100)}%`;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
// Utility Methods
|
|
797
|
+
showLoading() {
|
|
798
|
+
if (this.overlayElement) {
|
|
799
|
+
this.overlayElement.style.display = "flex";
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
hideLoading() {
|
|
803
|
+
if (this.overlayElement) {
|
|
804
|
+
this.overlayElement.style.display = "none";
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
showError(message) {
|
|
808
|
+
this.hideLoading();
|
|
809
|
+
if (this.errorElement) {
|
|
810
|
+
const errorMessageElement = this.errorElement.querySelector(".error-message");
|
|
811
|
+
if (errorMessageElement) {
|
|
812
|
+
errorMessageElement.textContent = message;
|
|
813
|
+
}
|
|
814
|
+
this.errorElement.style.display = "block";
|
|
815
|
+
}
|
|
816
|
+
console.error("PDF Viewer Error:", message);
|
|
817
|
+
}
|
|
818
|
+
downloadPDF() {
|
|
819
|
+
if (!this.pdfUrl) return;
|
|
820
|
+
const link = document.createElement("a");
|
|
821
|
+
link.href = this.pdfUrl;
|
|
822
|
+
link.download = this.title + ".pdf";
|
|
823
|
+
link.target = "_blank";
|
|
824
|
+
link.click();
|
|
825
|
+
}
|
|
826
|
+
// Public API
|
|
827
|
+
setPDF(pdfUrl, title = "") {
|
|
828
|
+
const oldPdfUrl = this.pdfUrl;
|
|
829
|
+
this.pdfUrl = pdfUrl;
|
|
830
|
+
this.title = title || "PDF Document";
|
|
831
|
+
this.isLoaded = false;
|
|
832
|
+
this.element.classList.remove("loaded");
|
|
833
|
+
if (this.pdfDoc) {
|
|
834
|
+
this.pdfDoc.destroy();
|
|
835
|
+
this.pdfDoc = null;
|
|
836
|
+
}
|
|
837
|
+
this.currentPage = 1;
|
|
838
|
+
this.totalPages = 0;
|
|
839
|
+
this.scale = 1;
|
|
840
|
+
if (pdfUrl) {
|
|
841
|
+
this.loadPDF();
|
|
842
|
+
}
|
|
843
|
+
const eventBus = this.getApp()?.events;
|
|
844
|
+
if (eventBus) {
|
|
845
|
+
eventBus.emit("pdfviewer:pdf-changed", {
|
|
846
|
+
viewer: this,
|
|
847
|
+
oldPdfUrl,
|
|
848
|
+
newPdfUrl: pdfUrl
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
getCurrentPage() {
|
|
853
|
+
return this.currentPage;
|
|
854
|
+
}
|
|
855
|
+
getTotalPages() {
|
|
856
|
+
return this.totalPages;
|
|
857
|
+
}
|
|
858
|
+
getCurrentScale() {
|
|
859
|
+
return this.scale;
|
|
860
|
+
}
|
|
861
|
+
async onBeforeDestroy() {
|
|
862
|
+
if (this.pdfDoc) {
|
|
863
|
+
this.pdfDoc.destroy();
|
|
864
|
+
this.pdfDoc = null;
|
|
865
|
+
}
|
|
866
|
+
this.pageRendering = false;
|
|
867
|
+
this.pageNumPending = null;
|
|
868
|
+
if (this._essentialListeners) {
|
|
869
|
+
this._essentialListeners.forEach(({ el, type, fn }) => {
|
|
870
|
+
if (el) el.removeEventListener(type, fn);
|
|
871
|
+
});
|
|
872
|
+
this._essentialListeners = null;
|
|
873
|
+
}
|
|
874
|
+
if (this._resizeObserver) {
|
|
875
|
+
this._resizeObserver.disconnect();
|
|
876
|
+
this._resizeObserver = null;
|
|
877
|
+
}
|
|
878
|
+
const eventBus = this.getApp()?.events;
|
|
879
|
+
if (eventBus) {
|
|
880
|
+
eventBus.emit("pdfviewer:destroyed", { viewer: this });
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
// Static method to show PDF in a fullscreen dialog
|
|
884
|
+
static async showDialog(pdfUrl, options = {}) {
|
|
885
|
+
const {
|
|
886
|
+
title = "PDF Viewer",
|
|
887
|
+
size = "fullscreen",
|
|
888
|
+
showControls = true,
|
|
889
|
+
allowZoom = true,
|
|
890
|
+
allowNavigation = true,
|
|
891
|
+
showPageNumbers = true,
|
|
892
|
+
...dialogOptions
|
|
893
|
+
} = options;
|
|
894
|
+
const viewer = new PDFViewer({
|
|
895
|
+
pdfUrl,
|
|
896
|
+
title,
|
|
897
|
+
showControls,
|
|
898
|
+
allowZoom,
|
|
899
|
+
allowNavigation,
|
|
900
|
+
showPageNumbers
|
|
901
|
+
});
|
|
902
|
+
const dialog = new Dialog({
|
|
903
|
+
title,
|
|
904
|
+
body: viewer,
|
|
905
|
+
size,
|
|
906
|
+
centered: true,
|
|
907
|
+
backdrop: "static",
|
|
908
|
+
keyboard: true,
|
|
909
|
+
buttons: [
|
|
910
|
+
{
|
|
911
|
+
text: "Download",
|
|
912
|
+
action: "download",
|
|
913
|
+
class: "btn btn-outline-primary"
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
text: "Close",
|
|
917
|
+
action: "close",
|
|
918
|
+
class: "btn btn-secondary",
|
|
919
|
+
dismiss: true
|
|
920
|
+
}
|
|
921
|
+
],
|
|
922
|
+
...dialogOptions
|
|
923
|
+
});
|
|
924
|
+
await dialog.render();
|
|
925
|
+
document.body.appendChild(dialog.element);
|
|
926
|
+
await dialog.mount();
|
|
927
|
+
dialog.show();
|
|
928
|
+
return new Promise((resolve) => {
|
|
929
|
+
dialog.on("hidden", () => {
|
|
930
|
+
dialog.destroy();
|
|
931
|
+
resolve(viewer);
|
|
932
|
+
});
|
|
933
|
+
dialog.on("action:download", () => {
|
|
934
|
+
viewer.downloadPDF();
|
|
935
|
+
});
|
|
936
|
+
dialog.on("action:close", () => {
|
|
937
|
+
dialog.hide();
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
export {
|
|
943
|
+
LightboxGallery as L,
|
|
944
|
+
PDFViewer as P
|
|
945
|
+
};
|
|
946
|
+
//# sourceMappingURL=PDFViewer-Dyo-Oeyd.js.map
|