@memberjunction/ng-artifacts 5.23.0 → 5.25.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,255 @@
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 } 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 "ag-grid-angular";
13
+ import * as i3 from "../file-artifact-toolbar.component";
14
+ const _forTrack0 = ($index, $item) => $item.name;
15
+ function XlsxArtifactViewerComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
16
+ i0.ɵɵelementStart(0, "div", 2);
17
+ i0.ɵɵelement(1, "i", 4);
18
+ i0.ɵɵelementStart(2, "span");
19
+ i0.ɵɵtext(3, "Loading workbook\u2026");
20
+ i0.ɵɵelementEnd()();
21
+ } }
22
+ function XlsxArtifactViewerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
23
+ i0.ɵɵelementStart(0, "div", 3);
24
+ i0.ɵɵelement(1, "i", 5);
25
+ i0.ɵɵelementStart(2, "span");
26
+ i0.ɵɵtext(3);
27
+ i0.ɵɵelementEnd()();
28
+ } if (rf & 2) {
29
+ const ctx_r0 = i0.ɵɵnextContext();
30
+ i0.ɵɵadvance(3);
31
+ i0.ɵɵtextInterpolate(ctx_r0.errorMessage);
32
+ } }
33
+ function XlsxArtifactViewerComponent_Conditional_4_Conditional_0_For_2_Template(rf, ctx) { if (rf & 1) {
34
+ const _r3 = i0.ɵɵgetCurrentView();
35
+ i0.ɵɵelementStart(0, "button", 10);
36
+ i0.ɵɵlistener("click", function XlsxArtifactViewerComponent_Conditional_4_Conditional_0_For_2_Template_button_click_0_listener() { const ɵ$index_25_r4 = i0.ɵɵrestoreView(_r3).$index; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.selectSheet(ɵ$index_25_r4)); });
37
+ i0.ɵɵelement(1, "i", 11);
38
+ i0.ɵɵtext(2);
39
+ i0.ɵɵelementEnd();
40
+ } if (rf & 2) {
41
+ const sheet_r5 = ctx.$implicit;
42
+ const ɵ$index_25_r4 = ctx.$index;
43
+ const ctx_r0 = i0.ɵɵnextContext(3);
44
+ i0.ɵɵclassProp("xlsx-viewer__tab--active", ɵ$index_25_r4 === ctx_r0.activeSheetIndex);
45
+ i0.ɵɵadvance(2);
46
+ i0.ɵɵtextInterpolate1(" ", sheet_r5.name, " ");
47
+ } }
48
+ function XlsxArtifactViewerComponent_Conditional_4_Conditional_0_Template(rf, ctx) { if (rf & 1) {
49
+ i0.ɵɵelementStart(0, "div", 6);
50
+ i0.ɵɵrepeaterCreate(1, XlsxArtifactViewerComponent_Conditional_4_Conditional_0_For_2_Template, 3, 3, "button", 9, _forTrack0);
51
+ i0.ɵɵelementEnd();
52
+ } if (rf & 2) {
53
+ const ctx_r0 = i0.ɵɵnextContext(2);
54
+ i0.ɵɵadvance();
55
+ i0.ɵɵrepeater(ctx_r0.sheets);
56
+ } }
57
+ function XlsxArtifactViewerComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
58
+ const _r2 = i0.ɵɵgetCurrentView();
59
+ i0.ɵɵconditionalCreate(0, XlsxArtifactViewerComponent_Conditional_4_Conditional_0_Template, 3, 0, "div", 6);
60
+ i0.ɵɵelementStart(1, "div", 7)(2, "ag-grid-angular", 8);
61
+ i0.ɵɵlistener("gridReady", function XlsxArtifactViewerComponent_Conditional_4_Template_ag_grid_angular_gridReady_2_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onGridReady($event)); });
62
+ i0.ɵɵelementEnd()();
63
+ } if (rf & 2) {
64
+ const ctx_r0 = i0.ɵɵnextContext();
65
+ i0.ɵɵconditional(ctx_r0.sheets.length > 1 ? 0 : -1);
66
+ i0.ɵɵadvance(2);
67
+ i0.ɵɵproperty("rowData", ctx_r0.activeSheet == null ? null : ctx_r0.activeSheet.rowData)("columnDefs", ctx_r0.activeSheet == null ? null : ctx_r0.activeSheet.columnDefs)("defaultColDef", ctx_r0.defaultColDef)("animateRows", false)("suppressMovableColumns", false)("enableCellTextSelection", true);
68
+ } }
69
+ /**
70
+ * Viewer plugin for Excel (XLSX/XLS) artifact versions stored in MJStorage.
71
+ *
72
+ * Downloads the file, parses all sheets with SheetJS, then renders the active
73
+ * sheet in an AG Grid. Sheet tabs let the user switch between sheets.
74
+ */
75
+ let XlsxArtifactViewerComponent = class XlsxArtifactViewerComponent extends BaseArtifactViewerPluginComponent {
76
+ fileService;
77
+ cdr;
78
+ isLoading = true;
79
+ isDownloading = false;
80
+ errorMessage = '';
81
+ sheets = [];
82
+ activeSheetIndex = 0;
83
+ defaultColDef = { resizable: true, sortable: true, filter: true, minWidth: 80 };
84
+ gridApi = null;
85
+ downloadUrl = '';
86
+ constructor(fileService, cdr) {
87
+ super();
88
+ this.fileService = fileService;
89
+ this.cdr = cdr;
90
+ }
91
+ get hasDisplayContent() { return true; }
92
+ get activeSheet() {
93
+ return this.sheets[this.activeSheetIndex] ?? null;
94
+ }
95
+ async ngOnInit() {
96
+ await this.loadWorkbook();
97
+ }
98
+ onGridReady(event) {
99
+ this.gridApi = event.api;
100
+ this.gridApi.sizeColumnsToFit();
101
+ }
102
+ selectSheet(index) {
103
+ this.activeSheetIndex = index;
104
+ this.cdr.markForCheck();
105
+ // Let Angular render the new rowData/columnDefs before re-sizing
106
+ setTimeout(() => this.gridApi?.sizeColumnsToFit(), 0);
107
+ }
108
+ async onDownload() {
109
+ if (!this.downloadUrl || this.isDownloading) {
110
+ return;
111
+ }
112
+ this.isDownloading = true;
113
+ this.cdr.markForCheck();
114
+ try {
115
+ await this.triggerBrowserDownload(this.downloadUrl, this.artifactVersion?.FileName || 'workbook.xlsx');
116
+ }
117
+ finally {
118
+ this.isDownloading = false;
119
+ this.cdr.markForCheck();
120
+ }
121
+ }
122
+ // ─── Private helpers ────────────────────────────────────────────────────────
123
+ async loadWorkbook() {
124
+ if (!this.artifactVersion?.ID) {
125
+ this.showError('No artifact version provided.');
126
+ return;
127
+ }
128
+ try {
129
+ let arrayBuffer;
130
+ if (this.artifactVersion.ContentMode === 'File') {
131
+ // File-backed: download from storage via pre-auth URL
132
+ this.downloadUrl = await this.fileService.getDownloadUrl(this.artifactVersion.ID);
133
+ arrayBuffer = await this.fetchAsArrayBuffer(this.downloadUrl);
134
+ }
135
+ else {
136
+ // Inline: content is a base64 data URL stored in the artifact version
137
+ const content = this.artifactVersion.Content;
138
+ if (!content) {
139
+ this.showError('Artifact has no content.');
140
+ return;
141
+ }
142
+ arrayBuffer = this.fileService.dataUrlToArrayBuffer(content);
143
+ // Create an object URL for download support
144
+ this.downloadUrl = this.fileService.dataUrlToObjectUrl(content, this.artifactVersion.MimeType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
145
+ }
146
+ const XLSX = await import('xlsx');
147
+ const workbook = XLSX.read(arrayBuffer, { type: 'array' });
148
+ this.sheets = this.parseWorkbook(workbook, XLSX);
149
+ this.isLoading = false;
150
+ this.cdr.markForCheck();
151
+ }
152
+ catch (err) {
153
+ this.showError(`Could not load workbook: ${err instanceof Error ? err.message : String(err)}`);
154
+ }
155
+ }
156
+ parseWorkbook(workbook, XLSX) {
157
+ return workbook.SheetNames.map(name => {
158
+ const sheet = workbook.Sheets[name];
159
+ const rows = XLSX.utils.sheet_to_json(sheet, {
160
+ defval: null,
161
+ raw: false, // Format dates/numbers as strings so AG Grid can display them
162
+ });
163
+ const headers = rows.length > 0 ? Object.keys(rows[0]) : [];
164
+ const columnDefs = headers.map(h => ({
165
+ field: h,
166
+ headerName: h,
167
+ tooltipField: h,
168
+ }));
169
+ return { name, rowData: rows, columnDefs };
170
+ });
171
+ }
172
+ async fetchAsArrayBuffer(url) {
173
+ const response = await fetch(url);
174
+ if (!response.ok) {
175
+ throw new Error(`HTTP ${response.status} fetching file`);
176
+ }
177
+ return response.arrayBuffer();
178
+ }
179
+ showError(message) {
180
+ this.isLoading = false;
181
+ this.errorMessage = message;
182
+ this.cdr.markForCheck();
183
+ }
184
+ static ɵfac = function XlsxArtifactViewerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || XlsxArtifactViewerComponent)(i0.ɵɵdirectiveInject(i1.ArtifactFileService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
185
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: XlsxArtifactViewerComponent, selectors: [["mj-xlsx-artifact-viewer"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 4, consts: [[1, "xlsx-viewer"], [3, "download", "fileName", "isDownloading", "showPrint"], [1, "xlsx-viewer__state"], [1, "xlsx-viewer__state", "xlsx-viewer__state--error"], [1, "fas", "fa-spinner", "fa-spin"], [1, "fas", "fa-exclamation-triangle"], [1, "xlsx-viewer__tabs"], [1, "xlsx-viewer__grid"], [1, "ag-theme-quartz", 3, "gridReady", "rowData", "columnDefs", "defaultColDef", "animateRows", "suppressMovableColumns", "enableCellTextSelection"], [1, "xlsx-viewer__tab", 3, "xlsx-viewer__tab--active"], [1, "xlsx-viewer__tab", 3, "click"], [1, "fas", "fa-table"]], template: function XlsxArtifactViewerComponent_Template(rf, ctx) { if (rf & 1) {
186
+ i0.ɵɵelementStart(0, "div", 0)(1, "mj-file-artifact-toolbar", 1);
187
+ i0.ɵɵlistener("download", function XlsxArtifactViewerComponent_Template_mj_file_artifact_toolbar_download_1_listener() { return ctx.onDownload(); });
188
+ i0.ɵɵelementEnd();
189
+ i0.ɵɵconditionalCreate(2, XlsxArtifactViewerComponent_Conditional_2_Template, 4, 0, "div", 2)(3, XlsxArtifactViewerComponent_Conditional_3_Template, 4, 1, "div", 3)(4, XlsxArtifactViewerComponent_Conditional_4_Template, 3, 7);
190
+ i0.ɵɵelementEnd();
191
+ } if (rf & 2) {
192
+ i0.ɵɵadvance();
193
+ i0.ɵɵproperty("fileName", (ctx.artifactVersion == null ? null : ctx.artifactVersion.FileName) || "workbook.xlsx")("isDownloading", ctx.isDownloading)("showPrint", false);
194
+ i0.ɵɵadvance();
195
+ i0.ɵɵconditional(ctx.isLoading ? 2 : ctx.errorMessage ? 3 : 4);
196
+ } }, dependencies: [i2.AgGridAngular, i3.FileArtifactToolbarComponent], styles: [".xlsx-viewer[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n flex: 1;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .xlsx-viewer__state--error[_ngcontent-%COMP%] {\n color: var(--mj-status-error-text);\n }\n\n .xlsx-viewer__tabs[_ngcontent-%COMP%] {\n display: flex;\n gap: 2px;\n padding: 4px 8px 0;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n overflow-x: auto;\n flex-shrink: 0;\n }\n\n .xlsx-viewer__tab[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n color: var(--mj-text-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.1s, color 0.1s;\n }\n\n .xlsx-viewer__tab[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .xlsx-viewer__tab--active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border-color: var(--mj-border-default);\n font-weight: 600;\n position: relative;\n }\n\n .xlsx-viewer__tab--active[_ngcontent-%COMP%]::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: 1px;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__grid[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n .xlsx-viewer__grid[_ngcontent-%COMP%] ag-grid-angular[_ngcontent-%COMP%] {\n height: 100%;\n width: 100%;\n display: block;\n }"] });
197
+ };
198
+ XlsxArtifactViewerComponent = __decorate([
199
+ RegisterClass(BaseArtifactViewerPluginComponent, 'XlsxArtifactViewerPlugin')
200
+ ], XlsxArtifactViewerComponent);
201
+ export { XlsxArtifactViewerComponent };
202
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(XlsxArtifactViewerComponent, [{
203
+ type: Component,
204
+ args: [{ standalone: false, selector: 'mj-xlsx-artifact-viewer', template: `
205
+ <div class="xlsx-viewer">
206
+ <mj-file-artifact-toolbar
207
+ [fileName]="artifactVersion?.FileName || 'workbook.xlsx'"
208
+ [isDownloading]="isDownloading"
209
+ [showPrint]="false"
210
+ (download)="onDownload()">
211
+ </mj-file-artifact-toolbar>
212
+
213
+ @if (isLoading) {
214
+ <div class="xlsx-viewer__state">
215
+ <i class="fas fa-spinner fa-spin"></i>
216
+ <span>Loading workbook…</span>
217
+ </div>
218
+ } @else if (errorMessage) {
219
+ <div class="xlsx-viewer__state xlsx-viewer__state--error">
220
+ <i class="fas fa-exclamation-triangle"></i>
221
+ <span>{{ errorMessage }}</span>
222
+ </div>
223
+ } @else {
224
+ @if (sheets.length > 1) {
225
+ <div class="xlsx-viewer__tabs">
226
+ @for (sheet of sheets; track sheet.name; let i = $index) {
227
+ <button
228
+ class="xlsx-viewer__tab"
229
+ [class.xlsx-viewer__tab--active]="i === activeSheetIndex"
230
+ (click)="selectSheet(i)">
231
+ <i class="fas fa-table"></i>
232
+ {{ sheet.name }}
233
+ </button>
234
+ }
235
+ </div>
236
+ }
237
+
238
+ <div class="xlsx-viewer__grid">
239
+ <ag-grid-angular
240
+ class="ag-theme-quartz"
241
+ [rowData]="activeSheet?.rowData"
242
+ [columnDefs]="activeSheet?.columnDefs"
243
+ [defaultColDef]="defaultColDef"
244
+ [animateRows]="false"
245
+ [suppressMovableColumns]="false"
246
+ [enableCellTextSelection]="true"
247
+ (gridReady)="onGridReady($event)">
248
+ </ag-grid-angular>
249
+ </div>
250
+ }
251
+ </div>
252
+ `, styles: ["\n .xlsx-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n flex: 1;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .xlsx-viewer__state--error {\n color: var(--mj-status-error-text);\n }\n\n .xlsx-viewer__tabs {\n display: flex;\n gap: 2px;\n padding: 4px 8px 0;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n overflow-x: auto;\n flex-shrink: 0;\n }\n\n .xlsx-viewer__tab {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n color: var(--mj-text-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.1s, color 0.1s;\n }\n\n .xlsx-viewer__tab:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .xlsx-viewer__tab--active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border-color: var(--mj-border-default);\n font-weight: 600;\n position: relative;\n }\n\n .xlsx-viewer__tab--active::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: 1px;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__grid {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n .xlsx-viewer__grid ag-grid-angular {\n height: 100%;\n width: 100%;\n display: block;\n }\n "] }]
253
+ }], () => [{ type: i1.ArtifactFileService }, { type: i0.ChangeDetectorRef }], null); })();
254
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(XlsxArtifactViewerComponent, { className: "XlsxArtifactViewerComponent", filePath: "src/lib/components/plugins/xlsx-artifact-viewer.component.ts", lineNumber: 158 }); })();
255
+ //# sourceMappingURL=xlsx-artifact-viewer.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xlsx-artifact-viewer.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/xlsx-artifact-viewer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnInit, ChangeDetectorRef } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';\nimport { ArtifactFileService } from '../../services/artifact-file.service';\nimport { ColDef, GridReadyEvent, GridApi } from 'ag-grid-community';\n\n/** A parsed sheet ready for display in AG Grid. */\ninterface SheetData {\n name: string;\n rowData: Record<string, string | number | boolean | null>[];\n columnDefs: ColDef[];\n}\n\n/**\n * Viewer plugin for Excel (XLSX/XLS) artifact versions stored in MJStorage.\n *\n * Downloads the file, parses all sheets with SheetJS, then renders the active\n * sheet in an AG Grid. Sheet tabs let the user switch between sheets.\n */\n@Component({\n standalone: false,\n selector: 'mj-xlsx-artifact-viewer',\n template: `\n <div class=\"xlsx-viewer\">\n <mj-file-artifact-toolbar\n [fileName]=\"artifactVersion?.FileName || 'workbook.xlsx'\"\n [isDownloading]=\"isDownloading\"\n [showPrint]=\"false\"\n (download)=\"onDownload()\">\n </mj-file-artifact-toolbar>\n\n @if (isLoading) {\n <div class=\"xlsx-viewer__state\">\n <i class=\"fas fa-spinner fa-spin\"></i>\n <span>Loading workbook…</span>\n </div>\n } @else if (errorMessage) {\n <div class=\"xlsx-viewer__state xlsx-viewer__state--error\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else {\n @if (sheets.length > 1) {\n <div class=\"xlsx-viewer__tabs\">\n @for (sheet of sheets; track sheet.name; let i = $index) {\n <button\n class=\"xlsx-viewer__tab\"\n [class.xlsx-viewer__tab--active]=\"i === activeSheetIndex\"\n (click)=\"selectSheet(i)\">\n <i class=\"fas fa-table\"></i>\n {{ sheet.name }}\n </button>\n }\n </div>\n }\n\n <div class=\"xlsx-viewer__grid\">\n <ag-grid-angular\n class=\"ag-theme-quartz\"\n [rowData]=\"activeSheet?.rowData\"\n [columnDefs]=\"activeSheet?.columnDefs\"\n [defaultColDef]=\"defaultColDef\"\n [animateRows]=\"false\"\n [suppressMovableColumns]=\"false\"\n [enableCellTextSelection]=\"true\"\n (gridReady)=\"onGridReady($event)\">\n </ag-grid-angular>\n </div>\n }\n </div>\n `,\n styles: [`\n .xlsx-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n flex: 1;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .xlsx-viewer__state--error {\n color: var(--mj-status-error-text);\n }\n\n .xlsx-viewer__tabs {\n display: flex;\n gap: 2px;\n padding: 4px 8px 0;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n overflow-x: auto;\n flex-shrink: 0;\n }\n\n .xlsx-viewer__tab {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n color: var(--mj-text-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.1s, color 0.1s;\n }\n\n .xlsx-viewer__tab:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .xlsx-viewer__tab--active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border-color: var(--mj-border-default);\n font-weight: 600;\n position: relative;\n }\n\n .xlsx-viewer__tab--active::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: 1px;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__grid {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n .xlsx-viewer__grid ag-grid-angular {\n height: 100%;\n width: 100%;\n display: block;\n }\n `]\n})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'XlsxArtifactViewerPlugin')\nexport class XlsxArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit {\n\n public isLoading = true;\n public isDownloading = false;\n public errorMessage = '';\n public sheets: SheetData[] = [];\n public activeSheetIndex = 0;\n public defaultColDef: ColDef = { resizable: true, sortable: true, filter: true, minWidth: 80 };\n\n private gridApi: GridApi | null = null;\n private downloadUrl = '';\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 public get activeSheet(): SheetData | null {\n return this.sheets[this.activeSheetIndex] ?? null;\n }\n\n async ngOnInit(): Promise<void> {\n await this.loadWorkbook();\n }\n\n public onGridReady(event: GridReadyEvent): void {\n this.gridApi = event.api;\n this.gridApi.sizeColumnsToFit();\n }\n\n public selectSheet(index: number): void {\n this.activeSheetIndex = index;\n this.cdr.markForCheck();\n // Let Angular render the new rowData/columnDefs before re-sizing\n setTimeout(() => this.gridApi?.sizeColumnsToFit(), 0);\n }\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(\n this.downloadUrl,\n this.artifactVersion?.FileName || 'workbook.xlsx',\n );\n } finally {\n this.isDownloading = false;\n this.cdr.markForCheck();\n }\n }\n\n // ─── Private helpers ────────────────────────────────────────────────────────\n\n private async loadWorkbook(): Promise<void> {\n if (!this.artifactVersion?.ID) {\n this.showError('No artifact version provided.');\n return;\n }\n\n try {\n let arrayBuffer: ArrayBuffer;\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 arrayBuffer = await this.fetchAsArrayBuffer(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 arrayBuffer = this.fileService.dataUrlToArrayBuffer(content);\n // Create an object URL for download support\n this.downloadUrl = this.fileService.dataUrlToObjectUrl(\n content,\n this.artifactVersion.MimeType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\n );\n }\n\n const XLSX = await import('xlsx') as unknown as XlsxModuleShim;\n const workbook = XLSX.read(arrayBuffer, { type: 'array' });\n this.sheets = this.parseWorkbook(workbook, XLSX);\n this.isLoading = false;\n this.cdr.markForCheck();\n } catch (err) {\n this.showError(`Could not load workbook: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n private parseWorkbook(workbook: WorkbookType, XLSX: XlsxModuleShim): SheetData[] {\n return workbook.SheetNames.map(name => {\n const sheet = workbook.Sheets[name];\n const rows = XLSX.utils.sheet_to_json<Record<string, string | number | boolean | null>>(sheet, {\n defval: null,\n raw: false, // Format dates/numbers as strings so AG Grid can display them\n });\n\n const headers = rows.length > 0 ? Object.keys(rows[0]) : [];\n const columnDefs: ColDef[] = headers.map(h => ({\n field: h,\n headerName: h,\n tooltipField: h,\n }));\n\n return { name, rowData: rows, columnDefs };\n });\n }\n\n private async fetchAsArrayBuffer(url: string): Promise<ArrayBuffer> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} fetching file`);\n }\n return response.arrayBuffer();\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 xlsx dynamic import ────────────────────────────────\n\ninterface WorkbookType {\n SheetNames: string[];\n Sheets: Record<string, unknown>;\n}\n\ninterface XlsxModuleShim {\n read(data: ArrayBuffer, opts: { type: 'array' | 'buffer' | 'binary' | 'base64' | 'string' }): WorkbookType;\n utils: {\n sheet_to_json<T>(sheet: unknown, opts: { defval: null; raw: boolean }): T[];\n };\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * Service for fetching pre-authenticated download URLs for file-mode artifact versions.
4
+ *
5
+ * URLs are cached in a bounded LRU-style Map (evicts oldest entry once the cap is reached)
6
+ * to avoid repeated round-trips for the same version within a session.
7
+ */
8
+ export declare class ArtifactFileService {
9
+ private readonly urlCache;
10
+ private static readonly DOWNLOAD_URL_QUERY;
11
+ /**
12
+ * Returns a pre-authenticated download URL for the given artifact version.
13
+ * Results are cached; pass `forceRefresh=true` to bypass the cache.
14
+ */
15
+ getDownloadUrl(artifactVersionId: string, forceRefresh?: boolean): Promise<string>;
16
+ /** Store a URL, evicting the oldest entry if the cache is at capacity. */
17
+ private cacheUrl;
18
+ /** Pre-fetch and cache a URL without awaiting in the calling context. */
19
+ prefetch(artifactVersionId: string): void;
20
+ /**
21
+ * Converts a base64 data URL (data:mime;base64,...) to an ArrayBuffer.
22
+ * Used by viewers to handle inline artifact content (ContentMode='Text').
23
+ */
24
+ dataUrlToArrayBuffer(dataUrl: string): ArrayBuffer;
25
+ /**
26
+ * Converts a base64 data URL to an object URL suitable for download/display.
27
+ * Used by viewers to create downloadable links for inline artifacts.
28
+ */
29
+ dataUrlToObjectUrl(dataUrl: string, mimeType: string): string;
30
+ static ɵfac: i0.ɵɵFactoryDeclaration<ArtifactFileService, never>;
31
+ static ɵprov: i0.ɵɵInjectableDeclaration<ArtifactFileService>;
32
+ }
33
+ //# sourceMappingURL=artifact-file.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-file.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/artifact-file.service.ts"],"names":[],"mappings":";AAMA;;;;;GAKG;AACH,qBAGa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IAEtD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAIxC;IAEF;;;OAGG;IACU,cAAc,CAAC,iBAAiB,EAAE,MAAM,EAAE,YAAY,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAsB7F,0EAA0E;IAC1E,OAAO,CAAC,QAAQ;IAWhB,yEAAyE;IAClE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IAMhD;;;OAGG;IACI,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW;IAWzD;;;OAGG;IACI,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;yCA1E3D,mBAAmB;6CAAnB,mBAAmB;CA+E/B"}
@@ -0,0 +1,87 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';
3
+ import * as i0 from "@angular/core";
4
+ /** Maximum number of URL entries to keep in the in-memory cache. */
5
+ const CACHE_MAX_SIZE = 20;
6
+ /**
7
+ * Service for fetching pre-authenticated download URLs for file-mode artifact versions.
8
+ *
9
+ * URLs are cached in a bounded LRU-style Map (evicts oldest entry once the cap is reached)
10
+ * to avoid repeated round-trips for the same version within a session.
11
+ */
12
+ export class ArtifactFileService {
13
+ urlCache = new Map();
14
+ static DOWNLOAD_URL_QUERY = `
15
+ query ArtifactFileDownloadUrl($artifactVersionId: String!) {
16
+ ArtifactFileDownloadUrl(artifactVersionId: $artifactVersionId)
17
+ }
18
+ `;
19
+ /**
20
+ * Returns a pre-authenticated download URL for the given artifact version.
21
+ * Results are cached; pass `forceRefresh=true` to bypass the cache.
22
+ */
23
+ async getDownloadUrl(artifactVersionId, forceRefresh = false) {
24
+ if (!forceRefresh) {
25
+ const cached = this.urlCache.get(artifactVersionId);
26
+ if (cached) {
27
+ return cached;
28
+ }
29
+ }
30
+ const result = await GraphQLDataProvider.ExecuteGQL(ArtifactFileService.DOWNLOAD_URL_QUERY, { artifactVersionId });
31
+ const url = result?.ArtifactFileDownloadUrl;
32
+ if (!url) {
33
+ throw new Error(`Failed to get download URL for artifact version ${artifactVersionId}`);
34
+ }
35
+ this.cacheUrl(artifactVersionId, url);
36
+ return url;
37
+ }
38
+ /** Store a URL, evicting the oldest entry if the cache is at capacity. */
39
+ cacheUrl(key, url) {
40
+ if (this.urlCache.size >= CACHE_MAX_SIZE) {
41
+ // Map preserves insertion order; delete the first (oldest) entry
42
+ const oldestKey = this.urlCache.keys().next().value;
43
+ if (oldestKey !== undefined) {
44
+ this.urlCache.delete(oldestKey);
45
+ }
46
+ }
47
+ this.urlCache.set(key, url);
48
+ }
49
+ /** Pre-fetch and cache a URL without awaiting in the calling context. */
50
+ prefetch(artifactVersionId) {
51
+ this.getDownloadUrl(artifactVersionId).catch(() => {
52
+ // Prefetch failures are silently ignored — the component will retry on display
53
+ });
54
+ }
55
+ /**
56
+ * Converts a base64 data URL (data:mime;base64,...) to an ArrayBuffer.
57
+ * Used by viewers to handle inline artifact content (ContentMode='Text').
58
+ */
59
+ dataUrlToArrayBuffer(dataUrl) {
60
+ const commaIndex = dataUrl.indexOf(',');
61
+ const base64 = commaIndex >= 0 ? dataUrl.slice(commaIndex + 1) : dataUrl;
62
+ const binary = atob(base64);
63
+ const bytes = new Uint8Array(binary.length);
64
+ for (let i = 0; i < binary.length; i++) {
65
+ bytes[i] = binary.charCodeAt(i);
66
+ }
67
+ return bytes.buffer;
68
+ }
69
+ /**
70
+ * Converts a base64 data URL to an object URL suitable for download/display.
71
+ * Used by viewers to create downloadable links for inline artifacts.
72
+ */
73
+ dataUrlToObjectUrl(dataUrl, mimeType) {
74
+ const arrayBuffer = this.dataUrlToArrayBuffer(dataUrl);
75
+ const blob = new Blob([arrayBuffer], { type: mimeType });
76
+ return URL.createObjectURL(blob);
77
+ }
78
+ static ɵfac = function ArtifactFileService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ArtifactFileService)(); };
79
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: ArtifactFileService, factory: ArtifactFileService.ɵfac, providedIn: 'root' });
80
+ }
81
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ArtifactFileService, [{
82
+ type: Injectable,
83
+ args: [{
84
+ providedIn: 'root'
85
+ }]
86
+ }], null, null); })();
87
+ //# sourceMappingURL=artifact-file.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-file.service.js","sourceRoot":"","sources":["../../../src/lib/services/artifact-file.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\n\n/** Maximum number of URL entries to keep in the in-memory cache. */\nconst CACHE_MAX_SIZE = 20;\n\n/**\n * Service for fetching pre-authenticated download URLs for file-mode artifact versions.\n *\n * URLs are cached in a bounded LRU-style Map (evicts oldest entry once the cap is reached)\n * to avoid repeated round-trips for the same version within a session.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class ArtifactFileService {\n\n private readonly urlCache = new Map<string, string>();\n\n private static readonly DOWNLOAD_URL_QUERY = `\n query ArtifactFileDownloadUrl($artifactVersionId: String!) {\n ArtifactFileDownloadUrl(artifactVersionId: $artifactVersionId)\n }\n `;\n\n /**\n * Returns a pre-authenticated download URL for the given artifact version.\n * Results are cached; pass `forceRefresh=true` to bypass the cache.\n */\n public async getDownloadUrl(artifactVersionId: string, forceRefresh = false): Promise<string> {\n if (!forceRefresh) {\n const cached = this.urlCache.get(artifactVersionId);\n if (cached) {\n return cached;\n }\n }\n\n const result = await GraphQLDataProvider.ExecuteGQL(\n ArtifactFileService.DOWNLOAD_URL_QUERY,\n { artifactVersionId },\n ) as { ArtifactFileDownloadUrl?: string };\n\n const url = result?.ArtifactFileDownloadUrl;\n if (!url) {\n throw new Error(`Failed to get download URL for artifact version ${artifactVersionId}`);\n }\n\n this.cacheUrl(artifactVersionId, url);\n return url;\n }\n\n /** Store a URL, evicting the oldest entry if the cache is at capacity. */\n private cacheUrl(key: string, url: string): void {\n if (this.urlCache.size >= CACHE_MAX_SIZE) {\n // Map preserves insertion order; delete the first (oldest) entry\n const oldestKey = this.urlCache.keys().next().value;\n if (oldestKey !== undefined) {\n this.urlCache.delete(oldestKey);\n }\n }\n this.urlCache.set(key, url);\n }\n\n /** Pre-fetch and cache a URL without awaiting in the calling context. */\n public prefetch(artifactVersionId: string): void {\n this.getDownloadUrl(artifactVersionId).catch(() => {\n // Prefetch failures are silently ignored — the component will retry on display\n });\n }\n\n /**\n * Converts a base64 data URL (data:mime;base64,...) to an ArrayBuffer.\n * Used by viewers to handle inline artifact content (ContentMode='Text').\n */\n public dataUrlToArrayBuffer(dataUrl: string): ArrayBuffer {\n const commaIndex = dataUrl.indexOf(',');\n const base64 = commaIndex >= 0 ? dataUrl.slice(commaIndex + 1) : dataUrl;\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n }\n\n /**\n * Converts a base64 data URL to an object URL suitable for download/display.\n * Used by viewers to create downloadable links for inline artifacts.\n */\n public dataUrlToObjectUrl(dataUrl: string, mimeType: string): string {\n const arrayBuffer = this.dataUrlToArrayBuffer(dataUrl);\n const blob = new Blob([arrayBuffer], { type: mimeType });\n return URL.createObjectURL(blob);\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  export * from './lib/artifacts.module';
2
2
  export * from './lib/services/artifact-icon.service';
3
+ export * from './lib/services/artifact-file.service';
3
4
  export * from './lib/interfaces/artifact-viewer-plugin.interface';
4
5
  export * from './lib/components/base-artifact-viewer.component';
5
6
  export * from './lib/components/artifact-type-plugin-viewer.component';
@@ -14,4 +15,8 @@ export * from './lib/components/plugins/svg-artifact-viewer.component';
14
15
  export * from './lib/components/plugins/component-artifact-viewer.component';
15
16
  export * from './lib/components/plugins/data-artifact-viewer.component';
16
17
  export * from './lib/components/plugins/component-feedback-panel/component-feedback-panel.component';
18
+ export * from './lib/components/file-artifact-toolbar.component';
19
+ export * from './lib/components/plugins/pdf-artifact-viewer.component';
20
+ export * from './lib/components/plugins/xlsx-artifact-viewer.component';
21
+ export * from './lib/components/plugins/docx-artifact-viewer.component';
17
22
  //# sourceMappingURL=public-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAKA,cAAc,wBAAwB,CAAC;AAGvC,cAAc,sCAAsC,CAAC;AAGrD,cAAc,mDAAmD,CAAC;AAGlE,cAAc,iDAAiD,CAAC;AAGhE,cAAc,wDAAwD,CAAC;AAGvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,qDAAqD,CAAC;AACpE,cAAc,kDAAkD,CAAC;AAGjE,cAAc,yDAAyD,CAAC;AACxE,cAAc,yDAAyD,CAAC;AACxE,cAAc,6DAA6D,CAAC;AAC5E,cAAc,yDAAyD,CAAC;AACxE,cAAc,wDAAwD,CAAC;AACvE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,yDAAyD,CAAC;AACxE,cAAc,sFAAsF,CAAC"}
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAKA,cAAc,wBAAwB,CAAC;AAGvC,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AAGrD,cAAc,mDAAmD,CAAC;AAGlE,cAAc,iDAAiD,CAAC;AAGhE,cAAc,wDAAwD,CAAC;AAGvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,qDAAqD,CAAC;AACpE,cAAc,kDAAkD,CAAC;AAGjE,cAAc,yDAAyD,CAAC;AACxE,cAAc,yDAAyD,CAAC;AACxE,cAAc,6DAA6D,CAAC;AAC5E,cAAc,yDAAyD,CAAC;AACxE,cAAc,wDAAwD,CAAC;AACvE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,yDAAyD,CAAC;AACxE,cAAc,sFAAsF,CAAC;AAGrG,cAAc,kDAAkD,CAAC;AACjE,cAAc,wDAAwD,CAAC;AACvE,cAAc,yDAAyD,CAAC;AACxE,cAAc,yDAAyD,CAAC"}
@@ -5,6 +5,7 @@
5
5
  export * from './lib/artifacts.module';
6
6
  // Services
7
7
  export * from './lib/services/artifact-icon.service';
8
+ export * from './lib/services/artifact-file.service';
8
9
  // Interfaces
9
10
  export * from './lib/interfaces/artifact-viewer-plugin.interface';
10
11
  // Base component
@@ -24,4 +25,9 @@ export * from './lib/components/plugins/svg-artifact-viewer.component';
24
25
  export * from './lib/components/plugins/component-artifact-viewer.component';
25
26
  export * from './lib/components/plugins/data-artifact-viewer.component';
26
27
  export * from './lib/components/plugins/component-feedback-panel/component-feedback-panel.component';
28
+ // File viewer toolbar and plugins
29
+ export * from './lib/components/file-artifact-toolbar.component';
30
+ export * from './lib/components/plugins/pdf-artifact-viewer.component';
31
+ export * from './lib/components/plugins/xlsx-artifact-viewer.component';
32
+ export * from './lib/components/plugins/docx-artifact-viewer.component';
27
33
  //# sourceMappingURL=public-api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,SAAS;AACT,cAAc,wBAAwB,CAAC;AAEvC,WAAW;AACX,cAAc,sCAAsC,CAAC;AAErD,aAAa;AACb,cAAc,mDAAmD,CAAC;AAElE,iBAAiB;AACjB,cAAc,iDAAiD,CAAC;AAEhE,8EAA8E;AAC9E,cAAc,wDAAwD,CAAC;AAEvE,gCAAgC;AAChC,cAAc,kDAAkD,CAAC;AACjE,cAAc,qDAAqD,CAAC;AACpE,cAAc,kDAAkD,CAAC;AAEjE,oBAAoB;AACpB,cAAc,yDAAyD,CAAC;AACxE,cAAc,yDAAyD,CAAC;AACxE,cAAc,6DAA6D,CAAC;AAC5E,cAAc,yDAAyD,CAAC;AACxE,cAAc,wDAAwD,CAAC;AACvE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,yDAAyD,CAAC;AACxE,cAAc,sFAAsF,CAAC","sourcesContent":["/*\n * Public API Surface of @memberjunction/ng-artifacts\n */\n\n// Module\nexport * from './lib/artifacts.module';\n\n// Services\nexport * from './lib/services/artifact-icon.service';\n\n// Interfaces\nexport * from './lib/interfaces/artifact-viewer-plugin.interface';\n\n// Base component\nexport * from './lib/components/base-artifact-viewer.component';\n\n// Artifact type plugin viewer (loads appropriate plugin based on DriverClass)\nexport * from './lib/components/artifact-type-plugin-viewer.component';\n\n// Artifact viewer UI components\nexport * from './lib/components/artifact-viewer-panel.component';\nexport * from './lib/components/artifact-version-history.component';\nexport * from './lib/components/artifact-message-card.component';\n\n// Plugin components\nexport * from './lib/components/plugins/json-artifact-viewer.component';\nexport * from './lib/components/plugins/code-artifact-viewer.component';\nexport * from './lib/components/plugins/markdown-artifact-viewer.component';\nexport * from './lib/components/plugins/html-artifact-viewer.component';\nexport * from './lib/components/plugins/svg-artifact-viewer.component';\nexport * from './lib/components/plugins/component-artifact-viewer.component';\nexport * from './lib/components/plugins/data-artifact-viewer.component';\nexport * from './lib/components/plugins/component-feedback-panel/component-feedback-panel.component';\n"]}
1
+ {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,SAAS;AACT,cAAc,wBAAwB,CAAC;AAEvC,WAAW;AACX,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AAErD,aAAa;AACb,cAAc,mDAAmD,CAAC;AAElE,iBAAiB;AACjB,cAAc,iDAAiD,CAAC;AAEhE,8EAA8E;AAC9E,cAAc,wDAAwD,CAAC;AAEvE,gCAAgC;AAChC,cAAc,kDAAkD,CAAC;AACjE,cAAc,qDAAqD,CAAC;AACpE,cAAc,kDAAkD,CAAC;AAEjE,oBAAoB;AACpB,cAAc,yDAAyD,CAAC;AACxE,cAAc,yDAAyD,CAAC;AACxE,cAAc,6DAA6D,CAAC;AAC5E,cAAc,yDAAyD,CAAC;AACxE,cAAc,wDAAwD,CAAC;AACvE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,yDAAyD,CAAC;AACxE,cAAc,sFAAsF,CAAC;AAErG,kCAAkC;AAClC,cAAc,kDAAkD,CAAC;AACjE,cAAc,wDAAwD,CAAC;AACvE,cAAc,yDAAyD,CAAC;AACxE,cAAc,yDAAyD,CAAC","sourcesContent":["/*\n * Public API Surface of @memberjunction/ng-artifacts\n */\n\n// Module\nexport * from './lib/artifacts.module';\n\n// Services\nexport * from './lib/services/artifact-icon.service';\nexport * from './lib/services/artifact-file.service';\n\n// Interfaces\nexport * from './lib/interfaces/artifact-viewer-plugin.interface';\n\n// Base component\nexport * from './lib/components/base-artifact-viewer.component';\n\n// Artifact type plugin viewer (loads appropriate plugin based on DriverClass)\nexport * from './lib/components/artifact-type-plugin-viewer.component';\n\n// Artifact viewer UI components\nexport * from './lib/components/artifact-viewer-panel.component';\nexport * from './lib/components/artifact-version-history.component';\nexport * from './lib/components/artifact-message-card.component';\n\n// Plugin components\nexport * from './lib/components/plugins/json-artifact-viewer.component';\nexport * from './lib/components/plugins/code-artifact-viewer.component';\nexport * from './lib/components/plugins/markdown-artifact-viewer.component';\nexport * from './lib/components/plugins/html-artifact-viewer.component';\nexport * from './lib/components/plugins/svg-artifact-viewer.component';\nexport * from './lib/components/plugins/component-artifact-viewer.component';\nexport * from './lib/components/plugins/data-artifact-viewer.component';\nexport * from './lib/components/plugins/component-feedback-panel/component-feedback-panel.component';\n\n// File viewer toolbar and plugins\nexport * from './lib/components/file-artifact-toolbar.component';\nexport * from './lib/components/plugins/pdf-artifact-viewer.component';\nexport * from './lib/components/plugins/xlsx-artifact-viewer.component';\nexport * from './lib/components/plugins/docx-artifact-viewer.component';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-artifacts",
3
- "version": "5.23.0",
3
+ "version": "5.25.0",
4
4
  "description": "MemberJunction: Artifact viewer plugin system for rendering different artifact types (JSON, Code, Markdown, HTML, SVG, Components)",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -32,22 +32,26 @@
32
32
  "dependencies": {
33
33
  "@angular/cdk": "21.1.3",
34
34
  "@angular/forms": "21.1.3",
35
- "@memberjunction/core": "5.23.0",
36
- "@memberjunction/core-entities": "5.23.0",
37
- "@memberjunction/global": "5.23.0",
38
- "@memberjunction/interactive-component-types": "5.23.0",
39
- "@memberjunction/ng-base-types": "5.23.0",
40
- "@memberjunction/ng-code-editor": "5.23.0",
41
- "@memberjunction/ng-notifications": "5.23.0",
42
- "@memberjunction/ng-react": "5.23.0",
43
- "@memberjunction/ng-pagination": "5.23.0",
44
- "@memberjunction/ng-shared-generic": "5.23.0",
45
- "@memberjunction/ng-query-viewer": "5.23.0",
46
- "@memberjunction/ng-trees": "5.23.0",
47
- "@memberjunction/ng-export-service": "5.23.0",
48
- "@memberjunction/ng-markdown": "5.23.0",
35
+ "@memberjunction/core": "5.25.0",
36
+ "@memberjunction/core-entities": "5.25.0",
37
+ "@memberjunction/global": "5.25.0",
38
+ "@memberjunction/interactive-component-types": "5.25.0",
39
+ "@memberjunction/ng-base-types": "5.25.0",
40
+ "@memberjunction/ng-code-editor": "5.25.0",
41
+ "@memberjunction/ng-notifications": "5.25.0",
42
+ "@memberjunction/ng-react": "5.25.0",
43
+ "@memberjunction/ng-pagination": "5.25.0",
44
+ "@memberjunction/ng-shared-generic": "5.25.0",
45
+ "@memberjunction/ng-query-viewer": "5.25.0",
46
+ "@memberjunction/ng-trees": "5.25.0",
47
+ "@memberjunction/ng-export-service": "5.25.0",
48
+ "@memberjunction/ng-markdown": "5.25.0",
49
+ "@memberjunction/graphql-dataprovider": "5.25.0",
50
+ "mammoth": "^1.8.0",
51
+ "pdfjs-dist": "^4.10.38",
49
52
  "rxjs": "^7.8.2",
50
- "tslib": "^2.8.1"
53
+ "tslib": "^2.8.1",
54
+ "xlsx": "^0.18.5"
51
55
  },
52
56
  "sideEffects": false,
53
57
  "repository": {