@memberjunction/ng-artifacts 5.24.0 → 5.26.0

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.
Files changed (37) hide show
  1. package/dist/lib/artifacts.module.d.ts +16 -11
  2. package/dist/lib/artifacts.module.d.ts.map +1 -1
  3. package/dist/lib/artifacts.module.js +40 -8
  4. package/dist/lib/artifacts.module.js.map +1 -1
  5. package/dist/lib/components/artifact-viewer-panel.component.d.ts +3 -0
  6. package/dist/lib/components/artifact-viewer-panel.component.d.ts.map +1 -1
  7. package/dist/lib/components/artifact-viewer-panel.component.js +13 -2
  8. package/dist/lib/components/artifact-viewer-panel.component.js.map +1 -1
  9. package/dist/lib/components/base-artifact-viewer.component.d.ts +6 -0
  10. package/dist/lib/components/base-artifact-viewer.component.d.ts.map +1 -1
  11. package/dist/lib/components/base-artifact-viewer.component.js +15 -0
  12. package/dist/lib/components/base-artifact-viewer.component.js.map +1 -1
  13. package/dist/lib/components/file-artifact-toolbar.component.d.ts +42 -0
  14. package/dist/lib/components/file-artifact-toolbar.component.d.ts.map +1 -0
  15. package/dist/lib/components/file-artifact-toolbar.component.js +249 -0
  16. package/dist/lib/components/file-artifact-toolbar.component.js.map +1 -0
  17. package/dist/lib/components/plugins/docx-artifact-viewer.component.d.ts +40 -0
  18. package/dist/lib/components/plugins/docx-artifact-viewer.component.d.ts.map +1 -0
  19. package/dist/lib/components/plugins/docx-artifact-viewer.component.js +223 -0
  20. package/dist/lib/components/plugins/docx-artifact-viewer.component.js.map +1 -0
  21. package/dist/lib/components/plugins/pdf-artifact-viewer.component.d.ts +68 -0
  22. package/dist/lib/components/plugins/pdf-artifact-viewer.component.d.ts.map +1 -0
  23. package/dist/lib/components/plugins/pdf-artifact-viewer.component.js +340 -0
  24. package/dist/lib/components/plugins/pdf-artifact-viewer.component.js.map +1 -0
  25. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.d.ts +44 -0
  26. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.d.ts.map +1 -0
  27. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js +255 -0
  28. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js.map +1 -0
  29. package/dist/lib/services/artifact-file.service.d.ts +33 -0
  30. package/dist/lib/services/artifact-file.service.d.ts.map +1 -0
  31. package/dist/lib/services/artifact-file.service.js +87 -0
  32. package/dist/lib/services/artifact-file.service.js.map +1 -0
  33. package/dist/public-api.d.ts +5 -0
  34. package/dist/public-api.d.ts.map +1 -1
  35. package/dist/public-api.js +6 -0
  36. package/dist/public-api.js.map +1 -1
  37. package/package.json +20 -16
@@ -0,0 +1,340 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Component, ViewChild } from '@angular/core';
8
+ import { RegisterClass } from '@memberjunction/global';
9
+ import { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';
10
+ import * as i0 from "@angular/core";
11
+ import * as i1 from "../../services/artifact-file.service";
12
+ import * as i2 from "../file-artifact-toolbar.component";
13
+ const _c0 = ["pdfCanvas"];
14
+ function PdfArtifactViewerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
15
+ i0.ɵɵelementStart(0, "div", 4);
16
+ i0.ɵɵelement(1, "i", 7);
17
+ i0.ɵɵelementStart(2, "span");
18
+ i0.ɵɵtext(3, "Loading PDF\u2026");
19
+ i0.ɵɵelementEnd()();
20
+ } }
21
+ function PdfArtifactViewerComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
22
+ i0.ɵɵelementStart(0, "div", 5);
23
+ i0.ɵɵelement(1, "i", 8);
24
+ i0.ɵɵelementStart(2, "span");
25
+ i0.ɵɵtext(3);
26
+ i0.ɵɵelementEnd()();
27
+ } if (rf & 2) {
28
+ const ctx_r0 = i0.ɵɵnextContext();
29
+ i0.ɵɵadvance(3);
30
+ i0.ɵɵtextInterpolate(ctx_r0.errorMessage);
31
+ } }
32
+ function PdfArtifactViewerComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
33
+ i0.ɵɵelementStart(0, "div", 6);
34
+ i0.ɵɵelement(1, "canvas", null, 0);
35
+ i0.ɵɵelementEnd();
36
+ } }
37
+ /**
38
+ * Viewer plugin for PDF artifact versions stored in MJStorage (ContentMode = 'File')
39
+ * or as inline base64 content (ContentMode = 'Inline').
40
+ *
41
+ * Renders one page at a time using PDF.js. The toolbar provides page navigation
42
+ * (including a direct page-jump input), zoom in/out, download, and print.
43
+ *
44
+ * Aspect-ratio preservation: the canvas backing buffer is sized at full HiDPI
45
+ * resolution while the CSS size uses `aspect-ratio` + `height: auto` so both
46
+ * dimensions scale proportionally. Pages wider than the panel scroll horizontally
47
+ * inside the body — no max-width clamping, which is required for zoom to work.
48
+ */
49
+ let PdfArtifactViewerComponent = class PdfArtifactViewerComponent extends BaseArtifactViewerPluginComponent {
50
+ fileService;
51
+ cdr;
52
+ canvasRef;
53
+ isLoading = true;
54
+ isDownloading = false;
55
+ errorMessage = '';
56
+ currentPage = 1;
57
+ totalPages = 1;
58
+ zoomLevel = 1.0;
59
+ pdfDoc = null;
60
+ renderTask = null;
61
+ downloadUrl = '';
62
+ _pdfjsLib = null;
63
+ /** Discrete zoom steps — 50 % to 400 %. */
64
+ ZOOM_STEPS = [0.5, 0.67, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0];
65
+ /** Base render scale (logical pixels per PDF point at zoom 1.0). */
66
+ BASE_SCALE = 1.5;
67
+ constructor(fileService, cdr) {
68
+ super();
69
+ this.fileService = fileService;
70
+ this.cdr = cdr;
71
+ }
72
+ get hasDisplayContent() { return true; }
73
+ /** Current zoom as an integer percentage for toolbar display. */
74
+ get zoomPercent() { return Math.round(this.zoomLevel * 100); }
75
+ get canZoomIn() {
76
+ return this.zoomLevel < this.ZOOM_STEPS[this.ZOOM_STEPS.length - 1];
77
+ }
78
+ get canZoomOut() {
79
+ return this.zoomLevel > this.ZOOM_STEPS[0];
80
+ }
81
+ async ngOnInit() {
82
+ await this.loadPdf();
83
+ }
84
+ ngOnDestroy() {
85
+ this.cancelCurrentRender();
86
+ this.pdfDoc?.destroy();
87
+ this.pdfDoc = null;
88
+ }
89
+ // ─── Navigation ─────────────────────────────────────────────────────────────
90
+ async goToNextPage() {
91
+ await this.onPageChange(this.currentPage + 1);
92
+ }
93
+ async goToPrevPage() {
94
+ await this.onPageChange(this.currentPage - 1);
95
+ }
96
+ async onPageChange(page) {
97
+ const clamped = Math.max(1, Math.min(page, this.totalPages));
98
+ if (clamped !== this.currentPage) {
99
+ this.currentPage = clamped;
100
+ await this.renderPage(this.currentPage);
101
+ }
102
+ }
103
+ // ─── Zoom ────────────────────────────────────────────────────────────────────
104
+ async onZoomIn() {
105
+ const next = this.ZOOM_STEPS.find(z => z > this.zoomLevel);
106
+ if (next !== undefined) {
107
+ this.zoomLevel = next;
108
+ await this.renderPage(this.currentPage);
109
+ }
110
+ }
111
+ async onZoomOut() {
112
+ for (let i = this.ZOOM_STEPS.length - 1; i >= 0; i--) {
113
+ if (this.ZOOM_STEPS[i] < this.zoomLevel) {
114
+ this.zoomLevel = this.ZOOM_STEPS[i];
115
+ await this.renderPage(this.currentPage);
116
+ return;
117
+ }
118
+ }
119
+ }
120
+ async onZoomReset() {
121
+ if (this.zoomLevel !== 1.0) {
122
+ this.zoomLevel = 1.0;
123
+ await this.renderPage(this.currentPage);
124
+ }
125
+ }
126
+ // ─── Download / Print ───────────────────────────────────────────────────────
127
+ async onDownload() {
128
+ if (!this.downloadUrl || this.isDownloading) {
129
+ return;
130
+ }
131
+ this.isDownloading = true;
132
+ this.cdr.markForCheck();
133
+ try {
134
+ await this.triggerBrowserDownload(this.downloadUrl, this.artifactVersion?.FileName || 'document.pdf');
135
+ }
136
+ finally {
137
+ this.isDownloading = false;
138
+ this.cdr.markForCheck();
139
+ }
140
+ }
141
+ onPrint() {
142
+ if (!this.downloadUrl) {
143
+ return;
144
+ }
145
+ // Open the PDF in a new tab so the browser's native print dialog handles it
146
+ window.open(this.downloadUrl, '_blank');
147
+ }
148
+ // ─── Private helpers ────────────────────────────────────────────────────────
149
+ async loadPdf() {
150
+ if (!this.artifactVersion?.ID) {
151
+ this.showError('No artifact version provided.');
152
+ return;
153
+ }
154
+ try {
155
+ await this.initPdfJs();
156
+ let pdfDoc;
157
+ if (this.artifactVersion.ContentMode === 'File') {
158
+ // File-backed: download from storage via pre-auth URL
159
+ this.downloadUrl = await this.fileService.getDownloadUrl(this.artifactVersion.ID);
160
+ pdfDoc = await this.loadPdfDocument(this.downloadUrl);
161
+ }
162
+ else {
163
+ // Inline: content is a base64 data URL stored in the artifact version
164
+ const content = this.artifactVersion.Content;
165
+ if (!content) {
166
+ this.showError('Artifact has no content.');
167
+ return;
168
+ }
169
+ const arrayBuffer = this.fileService.dataUrlToArrayBuffer(content);
170
+ pdfDoc = await this.loadPdfFromData(new Uint8Array(arrayBuffer));
171
+ // Create an object URL for download/print support
172
+ this.downloadUrl = this.fileService.dataUrlToObjectUrl(content, this.artifactVersion.MimeType || 'application/pdf');
173
+ }
174
+ this.pdfDoc = pdfDoc;
175
+ this.totalPages = pdfDoc.numPages;
176
+ this.currentPage = 1;
177
+ this.isLoading = false;
178
+ // detectChanges() forces a synchronous DOM update so the canvas element
179
+ // exists before renderPage() tries to draw on it.
180
+ this.cdr.detectChanges();
181
+ await this.setFitWidthZoom();
182
+ await this.renderPage(1);
183
+ }
184
+ catch (err) {
185
+ this.showError(`Could not load PDF: ${err instanceof Error ? err.message : String(err)}`);
186
+ }
187
+ }
188
+ /**
189
+ * Set the initial zoom so the first page fills the available panel width.
190
+ * Uses the exact ratio rather than snapping to a ZOOM_STEP so the page fits
191
+ * pixel-perfectly. Subsequent +/- presses will snap to the nearest step.
192
+ */
193
+ async setFitWidthZoom() {
194
+ if (!this.pdfDoc || !this.canvasRef?.nativeElement) {
195
+ return;
196
+ }
197
+ const page = await this.pdfDoc.getPage(1);
198
+ // naturalViewport.width is the CSS pixel width of the page at zoom = 1.0
199
+ const naturalViewport = page.getViewport({ scale: this.BASE_SCALE });
200
+ const body = this.canvasRef.nativeElement.closest('.pdf-viewer__body');
201
+ const availableWidth = (body?.clientWidth ?? 0) - 32; // 16px padding each side
202
+ if (availableWidth > 0 && naturalViewport.width > 0) {
203
+ const fitZoom = availableWidth / naturalViewport.width;
204
+ this.zoomLevel = Math.max(this.ZOOM_STEPS[0], Math.min(fitZoom, this.ZOOM_STEPS[this.ZOOM_STEPS.length - 1]));
205
+ }
206
+ }
207
+ /** Dynamically import pdfjs-dist and point it at the bundled worker. */
208
+ async initPdfJs() {
209
+ const pdfjsLib = await import('pdfjs-dist');
210
+ // pdfjs-dist v4 requires an explicit workerSrc. The worker file is copied to
211
+ // /assets/pdf.worker.min.mjs via the angular.json assets config in MJExplorer.
212
+ pdfjsLib.GlobalWorkerOptions.workerSrc = '/assets/pdf.worker.min.mjs';
213
+ this._pdfjsLib = pdfjsLib;
214
+ }
215
+ async loadPdfDocument(url) {
216
+ const pdfjsLib = this._pdfjsLib;
217
+ const loadingTask = pdfjsLib.getDocument(url);
218
+ return loadingTask.promise;
219
+ }
220
+ async loadPdfFromData(data) {
221
+ const pdfjsLib = this._pdfjsLib;
222
+ const loadingTask = pdfjsLib.getDocument({ data });
223
+ return loadingTask.promise;
224
+ }
225
+ async renderPage(pageNum) {
226
+ if (!this.pdfDoc || !this.canvasRef?.nativeElement) {
227
+ return;
228
+ }
229
+ this.cancelCurrentRender();
230
+ const page = await this.pdfDoc.getPage(pageNum);
231
+ const canvas = this.canvasRef.nativeElement;
232
+ const ctx = canvas.getContext('2d');
233
+ if (!ctx) {
234
+ return;
235
+ }
236
+ const pixelRatio = window.devicePixelRatio || 1;
237
+ // Scale = base * zoom * devicePixelRatio so backing buffer is always HiDPI-sharp.
238
+ const viewport = page.getViewport({ scale: this.BASE_SCALE * this.zoomLevel * pixelRatio });
239
+ // Backing buffer: full HiDPI resolution.
240
+ canvas.width = viewport.width;
241
+ canvas.height = viewport.height;
242
+ // CSS logical pixel dimensions at the current zoom level.
243
+ const cssWidth = viewport.width / pixelRatio;
244
+ const cssHeight = viewport.height / pixelRatio;
245
+ canvas.style.width = `${cssWidth}px`;
246
+ // aspect-ratio + height:auto: when cssWidth is set explicitly and the page
247
+ // renders at its natural size, the browser derives height proportionally.
248
+ // Pages wider than the panel scroll horizontally — the body handles overflow.
249
+ canvas.style.aspectRatio = `${cssWidth} / ${cssHeight}`;
250
+ canvas.style.height = 'auto';
251
+ const renderContext = { canvasContext: ctx, viewport };
252
+ this.renderTask = page.render(renderContext);
253
+ await this.renderTask.promise;
254
+ this.renderTask = null;
255
+ this.cdr.markForCheck();
256
+ }
257
+ cancelCurrentRender() {
258
+ if (this.renderTask) {
259
+ this.renderTask.cancel();
260
+ this.renderTask = null;
261
+ }
262
+ }
263
+ showError(message) {
264
+ this.isLoading = false;
265
+ this.errorMessage = message;
266
+ this.cdr.markForCheck();
267
+ }
268
+ static ɵfac = function PdfArtifactViewerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || PdfArtifactViewerComponent)(i0.ɵɵdirectiveInject(i1.ArtifactFileService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
269
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PdfArtifactViewerComponent, selectors: [["mj-pdf-artifact-viewer"]], viewQuery: function PdfArtifactViewerComponent_Query(rf, ctx) { if (rf & 1) {
270
+ i0.ɵɵviewQuery(_c0, 5);
271
+ } if (rf & 2) {
272
+ let _t;
273
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.canvasRef = _t.first);
274
+ } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 6, vars: 10, consts: [["pdfCanvas", ""], [1, "pdf-viewer"], [3, "download", "print", "prevPage", "nextPage", "pageChange", "zoomIn", "zoomOut", "zoomReset", "fileName", "currentPage", "totalPages", "isDownloading", "showPrint", "showZoom", "zoomPercent", "canZoomIn", "canZoomOut"], [1, "pdf-viewer__body"], [1, "pdf-viewer__state"], [1, "pdf-viewer__state", "pdf-viewer__state--error"], [1, "pdf-viewer__canvas-wrap"], [1, "fas", "fa-spinner", "fa-spin"], [1, "fas", "fa-exclamation-triangle"]], template: function PdfArtifactViewerComponent_Template(rf, ctx) { if (rf & 1) {
275
+ i0.ɵɵelementStart(0, "div", 1)(1, "mj-file-artifact-toolbar", 2);
276
+ i0.ɵɵlistener("download", function PdfArtifactViewerComponent_Template_mj_file_artifact_toolbar_download_1_listener() { return ctx.onDownload(); })("print", function PdfArtifactViewerComponent_Template_mj_file_artifact_toolbar_print_1_listener() { return ctx.onPrint(); })("prevPage", function PdfArtifactViewerComponent_Template_mj_file_artifact_toolbar_prevPage_1_listener() { return ctx.goToPrevPage(); })("nextPage", function PdfArtifactViewerComponent_Template_mj_file_artifact_toolbar_nextPage_1_listener() { return ctx.goToNextPage(); })("pageChange", function PdfArtifactViewerComponent_Template_mj_file_artifact_toolbar_pageChange_1_listener($event) { return ctx.onPageChange($event); })("zoomIn", function PdfArtifactViewerComponent_Template_mj_file_artifact_toolbar_zoomIn_1_listener() { return ctx.onZoomIn(); })("zoomOut", function PdfArtifactViewerComponent_Template_mj_file_artifact_toolbar_zoomOut_1_listener() { return ctx.onZoomOut(); })("zoomReset", function PdfArtifactViewerComponent_Template_mj_file_artifact_toolbar_zoomReset_1_listener() { return ctx.onZoomReset(); });
277
+ i0.ɵɵelementEnd();
278
+ i0.ɵɵelementStart(2, "div", 3);
279
+ i0.ɵɵconditionalCreate(3, PdfArtifactViewerComponent_Conditional_3_Template, 4, 0, "div", 4)(4, PdfArtifactViewerComponent_Conditional_4_Template, 4, 1, "div", 5)(5, PdfArtifactViewerComponent_Conditional_5_Template, 3, 0, "div", 6);
280
+ i0.ɵɵelementEnd()();
281
+ } if (rf & 2) {
282
+ i0.ɵɵadvance();
283
+ i0.ɵɵproperty("fileName", (ctx.artifactVersion == null ? null : ctx.artifactVersion.FileName) || "document.pdf")("currentPage", ctx.currentPage)("totalPages", ctx.totalPages)("isDownloading", ctx.isDownloading)("showPrint", true)("showZoom", true)("zoomPercent", ctx.zoomPercent)("canZoomIn", ctx.canZoomIn)("canZoomOut", ctx.canZoomOut);
284
+ i0.ɵɵadvance(2);
285
+ i0.ɵɵconditional(ctx.isLoading ? 3 : ctx.errorMessage ? 4 : 5);
286
+ } }, dependencies: [i2.FileArtifactToolbarComponent], styles: [".pdf-viewer[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface-sunken);\n }\n\n .pdf-viewer__body[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: auto;\n }\n\n .pdf-viewer__state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n height: 100%;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .pdf-viewer__state--error[_ngcontent-%COMP%] {\n color: var(--mj-status-error-text);\n }\n\n .pdf-viewer__canvas-wrap[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n padding: 16px;\n \n\n\n\n min-width: fit-content;\n }\n\n canvas[_ngcontent-%COMP%] {\n display: block;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 15%, transparent);\n \n\n\n\n\n }"] });
287
+ };
288
+ PdfArtifactViewerComponent = __decorate([
289
+ RegisterClass(BaseArtifactViewerPluginComponent, 'PdfArtifactViewerPlugin')
290
+ ], PdfArtifactViewerComponent);
291
+ export { PdfArtifactViewerComponent };
292
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PdfArtifactViewerComponent, [{
293
+ type: Component,
294
+ args: [{ standalone: false, selector: 'mj-pdf-artifact-viewer', template: `
295
+ <div class="pdf-viewer">
296
+ <mj-file-artifact-toolbar
297
+ [fileName]="artifactVersion?.FileName || 'document.pdf'"
298
+ [currentPage]="currentPage"
299
+ [totalPages]="totalPages"
300
+ [isDownloading]="isDownloading"
301
+ [showPrint]="true"
302
+ [showZoom]="true"
303
+ [zoomPercent]="zoomPercent"
304
+ [canZoomIn]="canZoomIn"
305
+ [canZoomOut]="canZoomOut"
306
+ (download)="onDownload()"
307
+ (print)="onPrint()"
308
+ (prevPage)="goToPrevPage()"
309
+ (nextPage)="goToNextPage()"
310
+ (pageChange)="onPageChange($event)"
311
+ (zoomIn)="onZoomIn()"
312
+ (zoomOut)="onZoomOut()"
313
+ (zoomReset)="onZoomReset()">
314
+ </mj-file-artifact-toolbar>
315
+
316
+ <div class="pdf-viewer__body">
317
+ @if (isLoading) {
318
+ <div class="pdf-viewer__state">
319
+ <i class="fas fa-spinner fa-spin"></i>
320
+ <span>Loading PDF…</span>
321
+ </div>
322
+ } @else if (errorMessage) {
323
+ <div class="pdf-viewer__state pdf-viewer__state--error">
324
+ <i class="fas fa-exclamation-triangle"></i>
325
+ <span>{{ errorMessage }}</span>
326
+ </div>
327
+ } @else {
328
+ <div class="pdf-viewer__canvas-wrap">
329
+ <canvas #pdfCanvas></canvas>
330
+ </div>
331
+ }
332
+ </div>
333
+ </div>
334
+ `, styles: ["\n .pdf-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface-sunken);\n }\n\n .pdf-viewer__body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n }\n\n .pdf-viewer__state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n height: 100%;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .pdf-viewer__state--error {\n color: var(--mj-status-error-text);\n }\n\n .pdf-viewer__canvas-wrap {\n display: flex;\n justify-content: center;\n padding: 16px;\n /* Prevent centered overflow from clipping the left side of zoomed pages.\n min-width: fit-content makes the wrap grow to fit the canvas so the\n parent overflow:auto container can scroll to both edges. */\n min-width: fit-content;\n }\n\n canvas {\n display: block;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 15%, transparent);\n /* Natural size \u2014 do NOT add max-width:100% here.\n renderPage sets width/aspect-ratio/height inline so the canvas renders at\n exactly the zoomed logical size. The scroll area (overflow:auto on the body)\n handles pages wider than the panel, which is the correct UX for zoom. */\n }\n "] }]
335
+ }], () => [{ type: i1.ArtifactFileService }, { type: i0.ChangeDetectorRef }], { canvasRef: [{
336
+ type: ViewChild,
337
+ args: ['pdfCanvas']
338
+ }] }); })();
339
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PdfArtifactViewerComponent, { className: "PdfArtifactViewerComponent", filePath: "src/lib/components/plugins/pdf-artifact-viewer.component.ts", lineNumber: 112 }); })();
340
+ //# sourceMappingURL=pdf-artifact-viewer.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-artifact-viewer.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/pdf-artifact-viewer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';\nimport { ArtifactFileService } from '../../services/artifact-file.service';\n\n/**\n * Viewer plugin for PDF artifact versions stored in MJStorage (ContentMode = 'File')\n * or as inline base64 content (ContentMode = 'Inline').\n *\n * Renders one page at a time using PDF.js. The toolbar provides page navigation\n * (including a direct page-jump input), zoom in/out, download, and print.\n *\n * Aspect-ratio preservation: the canvas backing buffer is sized at full HiDPI\n * resolution while the CSS size uses `aspect-ratio` + `height: auto` so both\n * dimensions scale proportionally. Pages wider than the panel scroll horizontally\n * inside the body — no max-width clamping, which is required for zoom to work.\n */\n@Component({\n standalone: false,\n selector: 'mj-pdf-artifact-viewer',\n template: `\n <div class=\"pdf-viewer\">\n <mj-file-artifact-toolbar\n [fileName]=\"artifactVersion?.FileName || 'document.pdf'\"\n [currentPage]=\"currentPage\"\n [totalPages]=\"totalPages\"\n [isDownloading]=\"isDownloading\"\n [showPrint]=\"true\"\n [showZoom]=\"true\"\n [zoomPercent]=\"zoomPercent\"\n [canZoomIn]=\"canZoomIn\"\n [canZoomOut]=\"canZoomOut\"\n (download)=\"onDownload()\"\n (print)=\"onPrint()\"\n (prevPage)=\"goToPrevPage()\"\n (nextPage)=\"goToNextPage()\"\n (pageChange)=\"onPageChange($event)\"\n (zoomIn)=\"onZoomIn()\"\n (zoomOut)=\"onZoomOut()\"\n (zoomReset)=\"onZoomReset()\">\n </mj-file-artifact-toolbar>\n\n <div class=\"pdf-viewer__body\">\n @if (isLoading) {\n <div class=\"pdf-viewer__state\">\n <i class=\"fas fa-spinner fa-spin\"></i>\n <span>Loading PDF…</span>\n </div>\n } @else if (errorMessage) {\n <div class=\"pdf-viewer__state pdf-viewer__state--error\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else {\n <div class=\"pdf-viewer__canvas-wrap\">\n <canvas #pdfCanvas></canvas>\n </div>\n }\n </div>\n </div>\n `,\n styles: [`\n .pdf-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface-sunken);\n }\n\n .pdf-viewer__body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n }\n\n .pdf-viewer__state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n height: 100%;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .pdf-viewer__state--error {\n color: var(--mj-status-error-text);\n }\n\n .pdf-viewer__canvas-wrap {\n display: flex;\n justify-content: center;\n padding: 16px;\n /* Prevent centered overflow from clipping the left side of zoomed pages.\n min-width: fit-content makes the wrap grow to fit the canvas so the\n parent overflow:auto container can scroll to both edges. */\n min-width: fit-content;\n }\n\n canvas {\n display: block;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 15%, transparent);\n /* Natural size — do NOT add max-width:100% here.\n renderPage sets width/aspect-ratio/height inline so the canvas renders at\n exactly the zoomed logical size. The scroll area (overflow:auto on the body)\n handles pages wider than the panel, which is the correct UX for zoom. */\n }\n `]\n})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'PdfArtifactViewerPlugin')\nexport class PdfArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit, OnDestroy {\n\n @ViewChild('pdfCanvas') canvasRef?: ElementRef<HTMLCanvasElement>;\n\n public isLoading = true;\n public isDownloading = false;\n public errorMessage = '';\n public currentPage = 1;\n public totalPages = 1;\n public zoomLevel = 1.0;\n\n private pdfDoc: PdfDocumentProxy | null = null;\n private renderTask: PdfRenderTask | null = null;\n private downloadUrl = '';\n private _pdfjsLib: PdfJsLib | null = null;\n\n /** Discrete zoom steps — 50 % to 400 %. */\n private readonly ZOOM_STEPS = [0.5, 0.67, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0];\n /** Base render scale (logical pixels per PDF point at zoom 1.0). */\n private readonly BASE_SCALE = 1.5;\n\n constructor(\n private fileService: ArtifactFileService,\n private cdr: ChangeDetectorRef,\n ) {\n super();\n }\n\n public override get hasDisplayContent(): boolean { return true; }\n\n /** Current zoom as an integer percentage for toolbar display. */\n public get zoomPercent(): number { return Math.round(this.zoomLevel * 100); }\n\n public get canZoomIn(): boolean {\n return this.zoomLevel < this.ZOOM_STEPS[this.ZOOM_STEPS.length - 1];\n }\n\n public get canZoomOut(): boolean {\n return this.zoomLevel > this.ZOOM_STEPS[0];\n }\n\n async ngOnInit(): Promise<void> {\n await this.loadPdf();\n }\n\n ngOnDestroy(): void {\n this.cancelCurrentRender();\n this.pdfDoc?.destroy();\n this.pdfDoc = null;\n }\n\n // ─── Navigation ─────────────────────────────────────────────────────────────\n\n public async goToNextPage(): Promise<void> {\n await this.onPageChange(this.currentPage + 1);\n }\n\n public async goToPrevPage(): Promise<void> {\n await this.onPageChange(this.currentPage - 1);\n }\n\n public async onPageChange(page: number): Promise<void> {\n const clamped = Math.max(1, Math.min(page, this.totalPages));\n if (clamped !== this.currentPage) {\n this.currentPage = clamped;\n await this.renderPage(this.currentPage);\n }\n }\n\n // ─── Zoom ────────────────────────────────────────────────────────────────────\n\n public async onZoomIn(): Promise<void> {\n const next = this.ZOOM_STEPS.find(z => z > this.zoomLevel);\n if (next !== undefined) {\n this.zoomLevel = next;\n await this.renderPage(this.currentPage);\n }\n }\n\n public async onZoomOut(): Promise<void> {\n for (let i = this.ZOOM_STEPS.length - 1; i >= 0; i--) {\n if (this.ZOOM_STEPS[i] < this.zoomLevel) {\n this.zoomLevel = this.ZOOM_STEPS[i];\n await this.renderPage(this.currentPage);\n return;\n }\n }\n }\n\n public async onZoomReset(): Promise<void> {\n if (this.zoomLevel !== 1.0) {\n this.zoomLevel = 1.0;\n await this.renderPage(this.currentPage);\n }\n }\n\n // ─── Download / Print ───────────────────────────────────────────────────────\n\n public async onDownload(): Promise<void> {\n if (!this.downloadUrl || this.isDownloading) {\n return;\n }\n this.isDownloading = true;\n this.cdr.markForCheck();\n try {\n await this.triggerBrowserDownload(this.downloadUrl, this.artifactVersion?.FileName || 'document.pdf');\n } finally {\n this.isDownloading = false;\n this.cdr.markForCheck();\n }\n }\n\n public onPrint(): void {\n if (!this.downloadUrl) {\n return;\n }\n // Open the PDF in a new tab so the browser's native print dialog handles it\n window.open(this.downloadUrl, '_blank');\n }\n\n // ─── Private helpers ────────────────────────────────────────────────────────\n\n private async loadPdf(): Promise<void> {\n if (!this.artifactVersion?.ID) {\n this.showError('No artifact version provided.');\n return;\n }\n\n try {\n await this.initPdfJs();\n let pdfDoc: PdfDocumentProxy;\n\n if (this.artifactVersion.ContentMode === 'File') {\n // File-backed: download from storage via pre-auth URL\n this.downloadUrl = await this.fileService.getDownloadUrl(this.artifactVersion.ID);\n pdfDoc = await this.loadPdfDocument(this.downloadUrl);\n } else {\n // Inline: content is a base64 data URL stored in the artifact version\n const content = this.artifactVersion.Content;\n if (!content) {\n this.showError('Artifact has no content.');\n return;\n }\n const arrayBuffer = this.fileService.dataUrlToArrayBuffer(content);\n pdfDoc = await this.loadPdfFromData(new Uint8Array(arrayBuffer));\n // Create an object URL for download/print support\n this.downloadUrl = this.fileService.dataUrlToObjectUrl(\n content,\n this.artifactVersion.MimeType || 'application/pdf'\n );\n }\n\n this.pdfDoc = pdfDoc;\n this.totalPages = pdfDoc.numPages;\n this.currentPage = 1;\n this.isLoading = false;\n // detectChanges() forces a synchronous DOM update so the canvas element\n // exists before renderPage() tries to draw on it.\n this.cdr.detectChanges();\n await this.setFitWidthZoom();\n await this.renderPage(1);\n } catch (err) {\n this.showError(`Could not load PDF: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n /**\n * Set the initial zoom so the first page fills the available panel width.\n * Uses the exact ratio rather than snapping to a ZOOM_STEP so the page fits\n * pixel-perfectly. Subsequent +/- presses will snap to the nearest step.\n */\n private async setFitWidthZoom(): Promise<void> {\n if (!this.pdfDoc || !this.canvasRef?.nativeElement) {\n return;\n }\n const page = await this.pdfDoc.getPage(1) as PdfPageProxy;\n // naturalViewport.width is the CSS pixel width of the page at zoom = 1.0\n const naturalViewport = page.getViewport({ scale: this.BASE_SCALE });\n const body = this.canvasRef.nativeElement.closest('.pdf-viewer__body') as HTMLElement | null;\n const availableWidth = (body?.clientWidth ?? 0) - 32; // 16px padding each side\n if (availableWidth > 0 && naturalViewport.width > 0) {\n const fitZoom = availableWidth / naturalViewport.width;\n this.zoomLevel = Math.max(\n this.ZOOM_STEPS[0],\n Math.min(fitZoom, this.ZOOM_STEPS[this.ZOOM_STEPS.length - 1])\n );\n }\n }\n\n /** Dynamically import pdfjs-dist and point it at the bundled worker. */\n private async initPdfJs(): Promise<void> {\n const pdfjsLib = await import('pdfjs-dist') as unknown as PdfJsLib;\n // pdfjs-dist v4 requires an explicit workerSrc. The worker file is copied to\n // /assets/pdf.worker.min.mjs via the angular.json assets config in MJExplorer.\n pdfjsLib.GlobalWorkerOptions.workerSrc = '/assets/pdf.worker.min.mjs';\n this._pdfjsLib = pdfjsLib;\n }\n\n private async loadPdfDocument(url: string): Promise<PdfDocumentProxy> {\n const pdfjsLib = this._pdfjsLib!;\n const loadingTask = pdfjsLib.getDocument(url);\n return loadingTask.promise;\n }\n\n private async loadPdfFromData(data: Uint8Array): Promise<PdfDocumentProxy> {\n const pdfjsLib = this._pdfjsLib!;\n const loadingTask = pdfjsLib.getDocument({ data });\n return loadingTask.promise;\n }\n\n private async renderPage(pageNum: number): Promise<void> {\n if (!this.pdfDoc || !this.canvasRef?.nativeElement) {\n return;\n }\n\n this.cancelCurrentRender();\n\n const page = await this.pdfDoc.getPage(pageNum) as PdfPageProxy;\n const canvas = this.canvasRef.nativeElement;\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return;\n }\n\n const pixelRatio = window.devicePixelRatio || 1;\n // Scale = base * zoom * devicePixelRatio so backing buffer is always HiDPI-sharp.\n const viewport = page.getViewport({ scale: this.BASE_SCALE * this.zoomLevel * pixelRatio });\n\n // Backing buffer: full HiDPI resolution.\n canvas.width = viewport.width;\n canvas.height = viewport.height;\n\n // CSS logical pixel dimensions at the current zoom level.\n const cssWidth = viewport.width / pixelRatio;\n const cssHeight = viewport.height / pixelRatio;\n\n canvas.style.width = `${cssWidth}px`;\n // aspect-ratio + height:auto: when cssWidth is set explicitly and the page\n // renders at its natural size, the browser derives height proportionally.\n // Pages wider than the panel scroll horizontally — the body handles overflow.\n canvas.style.aspectRatio = `${cssWidth} / ${cssHeight}`;\n canvas.style.height = 'auto';\n\n const renderContext = { canvasContext: ctx, viewport };\n this.renderTask = page.render(renderContext) as PdfRenderTask;\n await this.renderTask.promise;\n this.renderTask = null;\n this.cdr.markForCheck();\n }\n\n private cancelCurrentRender(): void {\n if (this.renderTask) {\n this.renderTask.cancel();\n this.renderTask = null;\n }\n }\n\n private showError(message: string): void {\n this.isLoading = false;\n this.errorMessage = message;\n this.cdr.markForCheck();\n }\n\n}\n\n\n// ─── Minimal type shims for pdfjs-dist dynamic import ─────────────────────────\n// We avoid a hard build-time dependency on pdfjs-dist types by declaring only\n// the surface we actually use.\n\ninterface PdfDocumentProxy {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfPageProxy>;\n destroy(): void;\n}\n\ninterface PdfPageProxy {\n getViewport(params: { scale: number }): { width: number; height: number };\n render(renderContext: { canvasContext: CanvasRenderingContext2D; viewport: { width: number; height: number } }): PdfRenderTask;\n}\n\ninterface PdfRenderTask {\n promise: Promise<void>;\n cancel(): void;\n}\n\ninterface PdfJsLib {\n GlobalWorkerOptions: { workerSrc: string };\n getDocument(source: string | { data: Uint8Array }): { promise: Promise<PdfDocumentProxy> };\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import { OnInit, ChangeDetectorRef } from '@angular/core';
2
+ import { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';
3
+ import { ArtifactFileService } from '../../services/artifact-file.service';
4
+ import { ColDef, GridReadyEvent } from 'ag-grid-community';
5
+ import * as i0 from "@angular/core";
6
+ /** A parsed sheet ready for display in AG Grid. */
7
+ interface SheetData {
8
+ name: string;
9
+ rowData: Record<string, string | number | boolean | null>[];
10
+ columnDefs: ColDef[];
11
+ }
12
+ /**
13
+ * Viewer plugin for Excel (XLSX/XLS) artifact versions stored in MJStorage.
14
+ *
15
+ * Downloads the file, parses all sheets with SheetJS, then renders the active
16
+ * sheet in an AG Grid. Sheet tabs let the user switch between sheets.
17
+ */
18
+ export declare class XlsxArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit {
19
+ private fileService;
20
+ private cdr;
21
+ isLoading: boolean;
22
+ isDownloading: boolean;
23
+ errorMessage: string;
24
+ sheets: SheetData[];
25
+ activeSheetIndex: number;
26
+ defaultColDef: ColDef;
27
+ private gridApi;
28
+ private downloadUrl;
29
+ constructor(fileService: ArtifactFileService, cdr: ChangeDetectorRef);
30
+ get hasDisplayContent(): boolean;
31
+ get activeSheet(): SheetData | null;
32
+ ngOnInit(): Promise<void>;
33
+ onGridReady(event: GridReadyEvent): void;
34
+ selectSheet(index: number): void;
35
+ onDownload(): Promise<void>;
36
+ private loadWorkbook;
37
+ private parseWorkbook;
38
+ private fetchAsArrayBuffer;
39
+ private showError;
40
+ static ɵfac: i0.ɵɵFactoryDeclaration<XlsxArtifactViewerComponent, never>;
41
+ static ɵcmp: i0.ɵɵComponentDeclaration<XlsxArtifactViewerComponent, "mj-xlsx-artifact-viewer", never, {}, {}, never, never, false, never>;
42
+ }
43
+ export {};
44
+ //# sourceMappingURL=xlsx-artifact-viewer.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xlsx-artifact-viewer.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/xlsx-artifact-viewer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,cAAc,EAAW,MAAM,mBAAmB,CAAC;;AAEpE,mDAAmD;AACnD,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC;IAC5D,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;GAKG;AACH,qBA0Ia,2BAA4B,SAAQ,iCAAkC,YAAW,MAAM;IAahG,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,GAAG;IAZN,SAAS,UAAQ;IACjB,aAAa,UAAS;IACtB,YAAY,SAAM;IAClB,MAAM,EAAE,SAAS,EAAE,CAAM;IACzB,gBAAgB,SAAK;IACrB,aAAa,EAAE,MAAM,CAAmE;IAE/F,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,WAAW,CAAM;gBAGf,WAAW,EAAE,mBAAmB,EAChC,GAAG,EAAE,iBAAiB;IAKhC,IAAoB,iBAAiB,IAAI,OAAO,CAAiB;IAEjE,IAAW,WAAW,IAAI,SAAS,GAAG,IAAI,CAEzC;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAKxC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAO1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAmB1B,YAAY;IAsC1B,OAAO,CAAC,aAAa;YAmBP,kBAAkB;IAQhC,OAAO,CAAC,SAAS;yCA7HN,2BAA2B;2CAA3B,2BAA2B;CAmIvC"}