@memberjunction/ng-artifacts 5.36.0 → 5.38.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 (50) hide show
  1. package/README.md +31 -0
  2. package/dist/__tests__/image-artifact-viewer.test.d.ts +2 -0
  3. package/dist/__tests__/image-artifact-viewer.test.d.ts.map +1 -0
  4. package/dist/__tests__/image-artifact-viewer.test.js +62 -0
  5. package/dist/__tests__/image-artifact-viewer.test.js.map +1 -0
  6. package/dist/lib/__tests__/interactive-form-apply.service.test.d.ts +2 -0
  7. package/dist/lib/__tests__/interactive-form-apply.service.test.d.ts.map +1 -0
  8. package/dist/lib/__tests__/interactive-form-apply.service.test.js +206 -0
  9. package/dist/lib/__tests__/interactive-form-apply.service.test.js.map +1 -0
  10. package/dist/lib/artifacts.module.d.ts +14 -12
  11. package/dist/lib/artifacts.module.d.ts.map +1 -1
  12. package/dist/lib/artifacts.module.js +14 -2
  13. package/dist/lib/artifacts.module.js.map +1 -1
  14. package/dist/lib/components/artifact-type-plugin-viewer.component.d.ts +11 -1
  15. package/dist/lib/components/artifact-type-plugin-viewer.component.d.ts.map +1 -1
  16. package/dist/lib/components/artifact-type-plugin-viewer.component.js +18 -1
  17. package/dist/lib/components/artifact-type-plugin-viewer.component.js.map +1 -1
  18. package/dist/lib/components/artifact-viewer-panel.component.d.ts +10 -1
  19. package/dist/lib/components/artifact-viewer-panel.component.d.ts.map +1 -1
  20. package/dist/lib/components/artifact-viewer-panel.component.js +11 -3
  21. package/dist/lib/components/artifact-viewer-panel.component.js.map +1 -1
  22. package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts +60 -2
  23. package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts.map +1 -1
  24. package/dist/lib/components/plugins/component-artifact-viewer.component.js +373 -24
  25. package/dist/lib/components/plugins/component-artifact-viewer.component.js.map +1 -1
  26. package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts.map +1 -1
  27. package/dist/lib/components/plugins/data-artifact-viewer.component.js +0 -1
  28. package/dist/lib/components/plugins/data-artifact-viewer.component.js.map +1 -1
  29. package/dist/lib/components/plugins/docx-artifact-viewer.component.js +2 -2
  30. package/dist/lib/components/plugins/docx-artifact-viewer.component.js.map +1 -1
  31. package/dist/lib/components/plugins/image-artifact-viewer.component.d.ts +129 -0
  32. package/dist/lib/components/plugins/image-artifact-viewer.component.d.ts.map +1 -0
  33. package/dist/lib/components/plugins/image-artifact-viewer.component.js +413 -0
  34. package/dist/lib/components/plugins/image-artifact-viewer.component.js.map +1 -0
  35. package/dist/lib/components/plugins/pdf-artifact-viewer.component.js +2 -2
  36. package/dist/lib/components/plugins/pdf-artifact-viewer.component.js.map +1 -1
  37. package/dist/lib/components/plugins/save-query-dialog.component.d.ts.map +1 -1
  38. package/dist/lib/components/plugins/save-query-dialog.component.js +0 -2
  39. package/dist/lib/components/plugins/save-query-dialog.component.js.map +1 -1
  40. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js +2 -2
  41. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js.map +1 -1
  42. package/dist/lib/services/interactive-form-apply.service.d.ts +46 -0
  43. package/dist/lib/services/interactive-form-apply.service.d.ts.map +1 -0
  44. package/dist/lib/services/interactive-form-apply.service.js +219 -0
  45. package/dist/lib/services/interactive-form-apply.service.js.map +1 -0
  46. package/dist/public-api.d.ts +2 -0
  47. package/dist/public-api.d.ts.map +1 -1
  48. package/dist/public-api.js +2 -0
  49. package/dist/public-api.js.map +1 -1
  50. package/package.json +18 -16
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-artifact-viewer.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/image-artifact-viewer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';\nimport { DataSnapshot } from '@memberjunction/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';\nimport { ArtifactFileService } from '../../services/artifact-file.service';\n\n/**\n * Lookup table for synthesizing a download filename when the artifact version\n * has no `FileName` set. Covers the MIME types declared by the seed `Image`\n * artifact type (`image/*`); anything not listed falls back to `img`.\n *\n * Exported so the unit test can assert the mapping without instantiating an\n * Angular component (the package's vitest config uses the `node` environment).\n */\nexport const IMAGE_MIME_EXTENSION_MAP: Readonly<Record<string, string>> = Object.freeze({\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/gif': 'gif',\n 'image/webp': 'webp',\n 'image/bmp': 'bmp',\n 'image/tiff': 'tif',\n 'image/avif': 'avif',\n 'image/heic': 'heic',\n 'image/heif': 'heif',\n});\n\n/**\n * Pure helper that synthesizes a safe download filename from an artifact's\n * name + MIME type. Pulled out as a top-level function (not a class method) so\n * the vitest `node`-environment unit test can exercise it directly.\n *\n * - Strips characters illegal on common filesystems (anything not\n * `[A-Za-z0-9._-]`) and replaces them with underscores.\n * - Picks the extension from {@link IMAGE_MIME_EXTENSION_MAP}, defaulting to\n * `img` if the MIME type is missing or unrecognized.\n * - Falls back to the literal `image` when no name is supplied.\n *\n * @param name Artifact name (e.g. `\"Generated image 1\"`). Optional.\n * @param mime MIME type from the artifact version (e.g. `\"image/jpeg\"`). Optional.\n * @returns Sanitized filename with extension (e.g. `\"Generated_image_1.jpg\"`).\n */\nexport function buildImageDownloadFileName(name: string | null | undefined, mime: string | null | undefined): string {\n // `??` falls back on null/undefined but NOT empty / whitespace-only names —\n // those should also use the default, so trim first and check for length.\n const trimmed = name?.trim();\n const baseName = trimmed && trimmed.length > 0 ? trimmed : 'image';\n const safeBase = baseName.replace(/[^A-Za-z0-9._-]/g, '_');\n const normalizedMime = (mime ?? '').toLowerCase();\n const extension = IMAGE_MIME_EXTENSION_MAP[normalizedMime] ?? 'img';\n // If the name already ends with the right extension, don't append it again\n // (e.g. metadata round-tripped through a file upload).\n return safeBase.toLowerCase().endsWith(`.${extension}`) ? safeBase : `${safeBase}.${extension}`;\n}\n\n/**\n * Viewer plugin for raster image artifact versions (`image/png`, `image/jpeg`,\n * `image/webp`, etc.). Vector SVG is handled by `SvgArtifactViewerPlugin`.\n *\n * Storage modes both supported (same contract as `PdfArtifactViewerPlugin`):\n * - `ContentMode === 'Text'` — `artifactVersion.Content` is a `data:image/<mime>;base64,…` URI.\n * We bind it directly to `<img [src]>`; the browser decodes inline.\n * - `ContentMode === 'File'` — bytes live in MJStorage. We fetch a\n * pre-authenticated URL via `ArtifactFileService.getDownloadUrl()` and\n * bind that to `<img [src]>`.\n *\n * UX highlights:\n * - Click the image to toggle **fit** (default — `object-fit: contain` inside\n * the panel) ↔ **actual** size (1:1 pixels, scrolls within the panel body).\n * - Toolbar provides Download (reuses base class `triggerBrowserDownload`) and\n * Print (opens in a new tab so the browser's native print dialog handles it).\n * - Loading and error states match the PDF plugin's vocabulary, so behavior\n * across all file-style viewers is consistent.\n * - Image dimensions are surfaced as a small chip once the browser has decoded\n * the image (`naturalWidth × naturalHeight`).\n *\n * Design tokens are used for every color so the plugin adapts to light / dark\n * themes — see root `CLAUDE.md` \"Design Token System\".\n *\n * @see PdfArtifactViewerComponent — the dual-storage reference implementation.\n * @see plans/artifact-attachment-unification.md — the broader artifact-as-canonical-storage design.\n * @see docs/chat-ui-image-display-bugs.md — context for why this plugin exists (Bug B).\n */\n@Component({\n standalone: false,\n selector: 'mj-image-artifact-viewer',\n template: `\n <div class=\"image-viewer\" [ngClass]=\"cssClass\">\n <mj-file-artifact-toolbar\n [fileName]=\"toolbarFileName\"\n [isDownloading]=\"isDownloading\"\n [showPrint]=\"true\"\n (download)=\"onDownload()\"\n (print)=\"onPrint()\"\n >\n </mj-file-artifact-toolbar>\n\n <div\n class=\"image-viewer__body\"\n [class.image-viewer__body--actual]=\"displayMode === 'actual'\"\n >\n @if (errorMessage) {\n <div class=\"image-viewer__state image-viewer__state--error\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else {\n @if (isLoading) {\n <div class=\"image-viewer__state\">\n <i class=\"fas fa-spinner fa-spin\"></i>\n <span>Loading image…</span>\n </div>\n }\n @if (imageUrl) {\n <!-- The img is rendered even while loading=true (with opacity 0)\n so the browser starts decoding immediately. The (load) handler\n flips isLoading=false once dimensions are known. -->\n <img\n class=\"image-viewer__img\"\n [class.image-viewer__img--actual]=\"displayMode === 'actual'\"\n [class.image-viewer__img--hidden]=\"isLoading\"\n [src]=\"imageUrl\"\n [alt]=\"imageAltText\"\n [title]=\"\n displayMode === 'fit'\n ? 'Click to view at actual size'\n : 'Click to fit to window'\n \"\n (load)=\"onImageLoad($event)\"\n (error)=\"onImageError()\"\n (click)=\"toggleDisplayMode()\"\n />\n }\n }\n </div>\n\n @if (naturalWidth && naturalHeight && !isLoading && !errorMessage) {\n <div class=\"image-viewer__dimensions\">{{ naturalWidth }} × {{ naturalHeight }}</div>\n }\n </div>\n `,\n styles: [\n `\n :host {\n display: block;\n height: 100%;\n }\n\n .image-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface-sunken);\n position: relative;\n }\n\n .image-viewer__body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n }\n\n /* \"Actual size\" mode: allow scrolling from the top-left so large images\n don't get re-centered as the user pans. The browser handles overflow. */\n .image-viewer__body--actual {\n align-items: flex-start;\n justify-content: flex-start;\n }\n\n .image-viewer__state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .image-viewer__state--error {\n color: var(--mj-status-error-text);\n }\n\n .image-viewer__img {\n /* Fit mode (default): never exceeds the panel, preserves aspect ratio. */\n max-width: 100%;\n max-height: 100%;\n width: auto;\n height: auto;\n object-fit: contain;\n cursor: zoom-in;\n background: var(--mj-bg-surface);\n border-radius: 4px;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n transition: opacity 0.18s ease-out;\n }\n\n /* Actual-size mode: render at the image's intrinsic dimensions so the body\n scroll-area handles anything larger than the panel. */\n .image-viewer__img--actual {\n max-width: none;\n max-height: none;\n width: auto;\n height: auto;\n cursor: zoom-out;\n }\n\n /* Pre-load: keep the <img> in the DOM (so the browser decodes it) but\n invisible and out of layout flow so the spinner sits centered. */\n .image-viewer__img--hidden {\n opacity: 0;\n position: absolute;\n pointer-events: none;\n }\n\n .image-viewer__dimensions {\n position: absolute;\n bottom: 8px;\n right: 12px;\n padding: 2px 8px;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 90%, transparent);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n color: var(--mj-text-muted);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n pointer-events: none;\n /* Don't intercept clicks meant for the image-toggle. */\n }\n `,\n ],\n})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'ImageArtifactViewerPlugin')\nexport class ImageArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit, OnDestroy {\n /** Current resolved URL to bind to the `<img src>` — either a data URI (inline) or a pre-auth URL (file). */\n public imageUrl = '';\n\n /** True from `ngOnInit` until the `<img>` `(load)` event fires (or an error happens). */\n public isLoading = true;\n\n /** Set while the download is in flight, to drive the toolbar spinner. */\n public isDownloading = false;\n\n /** User-facing error message; non-empty hides the image and shows the error block. */\n public errorMessage = '';\n\n /** Click-to-toggle display mode. `'fit'` = contained in the panel, `'actual'` = 1:1 with scroll. */\n public displayMode: 'fit' | 'actual' = 'fit';\n\n /** Native image dimensions, populated by the `(load)` event. Null until then. */\n public naturalWidth: number | null = null;\n public naturalHeight: number | null = null;\n\n /**\n * Object URL we lazily build from inline data so Download / Print have a real\n * URL to anchor on. Tracked here so `ngOnDestroy` can release it via\n * `URL.revokeObjectURL`. Null until the user clicks Download or Print.\n */\n private inlineObjectUrl: string | null = null;\n\n constructor(\n private fileService: ArtifactFileService,\n private cdr: ChangeDetectorRef,\n ) {\n super();\n }\n\n /** Opt in to the wrapper's \"Display\" tab — without this the panel only shows Details/JSON. */\n public override get hasDisplayContent(): boolean {\n return true;\n }\n\n /** Filename surfaced by the toolbar in the center of the bar. */\n public get toolbarFileName(): string {\n return this.artifactVersion?.FileName || this.artifactVersion?.Name || 'image';\n }\n\n /** Accessible alt text — prefers a descriptive name over a raw filename. */\n public get imageAltText(): string {\n return this.artifactVersion?.Name || this.artifactVersion?.FileName || 'Image artifact';\n }\n\n async ngOnInit(): Promise<void> {\n await this.resolveImageUrl();\n }\n\n ngOnDestroy(): void {\n // Release any blob URL we built for Download / Print so the browser\n // doesn't hold the raw bytes in memory after the panel closes.\n if (this.inlineObjectUrl) {\n URL.revokeObjectURL(this.inlineObjectUrl);\n this.inlineObjectUrl = null;\n }\n }\n\n // ─── Loading ────────────────────────────────────────────────────────────────\n\n /**\n * Resolve the image URL based on the artifact version's `ContentMode`.\n * Sets `imageUrl` (which the template binds to `<img src>`) or `errorMessage`.\n * Loading state remains `true` until the browser fires `(load)` on the `<img>`.\n */\n private async resolveImageUrl(): Promise<void> {\n if (!this.artifactVersion?.ID) {\n this.showError('No artifact version provided.');\n return;\n }\n\n // Defensive MIME check — refuse to render non-image artifacts. If we get here\n // it's a metadata bug somewhere (the ArtifactType registry routed something\n // non-image to the image plugin), and silently showing a broken-image icon\n // would mask the real issue.\n const mime = (this.artifactVersion.MimeType ?? '').toLowerCase();\n if (mime && !mime.startsWith('image/')) {\n this.showError(`This viewer cannot display content of type \"${this.artifactVersion.MimeType}\".`);\n return;\n }\n\n try {\n if (this.artifactVersion.ContentMode === 'File') {\n // File-backed: fetch a pre-auth URL from MJStorage via the shared service.\n // ArtifactFileService caches the URL so reopening the same artifact is instant.\n this.imageUrl = await this.fileService.getDownloadUrl(this.artifactVersion.ID);\n } else {\n // Inline ('Text' mode): Content is a data URI ready to bind directly.\n const content = this.artifactVersion.Content;\n if (!content) {\n this.showError('This artifact has no image content.');\n return;\n }\n this.imageUrl = content;\n }\n this.cdr.markForCheck();\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n this.showError(`Could not load image: ${detail}`);\n }\n }\n\n public onImageLoad(event: Event): void {\n const img = event.target as HTMLImageElement;\n this.naturalWidth = img.naturalWidth || null;\n this.naturalHeight = img.naturalHeight || null;\n this.isLoading = false;\n this.cdr.markForCheck();\n }\n\n public onImageError(): void {\n this.showError('The image could not be displayed. It may be corrupt or in an unsupported format.');\n }\n\n private showError(message: string): void {\n this.isLoading = false;\n this.errorMessage = message;\n this.cdr.markForCheck();\n }\n\n // ─── User actions ───────────────────────────────────────────────────────────\n\n public toggleDisplayMode(): void {\n // Don't toggle while loading or errored — the user has nothing to react to yet.\n if (this.isLoading || this.errorMessage) {\n return;\n }\n this.displayMode = this.displayMode === 'fit' ? 'actual' : 'fit';\n this.cdr.markForCheck();\n }\n\n /**\n * Download the image to disk. Uses the inherited `triggerBrowserDownload`\n * helper so the fetch → blob → object-URL → anchor pattern is shared across\n * every file-backed viewer (PDF, XLSX, DOCX, and now Image).\n */\n public async onDownload(): Promise<void> {\n if (this.isDownloading || !this.imageUrl) {\n return;\n }\n this.isDownloading = true;\n this.cdr.markForCheck();\n try {\n const sourceUrl = this.ensureDownloadableUrl();\n const fileName = buildImageDownloadFileName(\n this.artifactVersion?.FileName ?? this.artifactVersion?.Name ?? null,\n this.artifactVersion?.MimeType ?? null,\n );\n await this.triggerBrowserDownload(sourceUrl, fileName);\n } finally {\n this.isDownloading = false;\n this.cdr.markForCheck();\n }\n }\n\n /**\n * Open the image in a new browser tab so the user can use the browser's\n * native print dialog. We deliberately don't manage a print pipeline\n * ourselves — same approach as `PdfArtifactViewerComponent.onPrint()`.\n */\n public onPrint(): void {\n if (!this.imageUrl) {\n return;\n }\n const sourceUrl = this.ensureDownloadableUrl();\n window.open(sourceUrl, '_blank');\n }\n\n /**\n * Returns a URL suitable for `<a download>` / `window.open` use.\n * - File mode: `imageUrl` is already a pre-auth blob URL — use it directly.\n * - Inline mode: `imageUrl` is a data URI; we lazily turn it into a blob URL\n * (cached on the instance) for nicer download UX. Some browsers truncate\n * long data URIs in the downloads tray; blob URLs preserve the filename.\n */\n private ensureDownloadableUrl(): string {\n if (this.artifactVersion?.ContentMode === 'File') {\n return this.imageUrl;\n }\n if (this.inlineObjectUrl) {\n return this.inlineObjectUrl;\n }\n const mime = this.artifactVersion?.MimeType || 'application/octet-stream';\n this.inlineObjectUrl = this.fileService.dataUrlToObjectUrl(this.imageUrl, mime);\n return this.inlineObjectUrl;\n }\n\n // ─── Snapshot ───────────────────────────────────────────────────────────────\n\n /**\n * Required by the base class. Returns a structured summary of the artifact\n * in its current display state — used by snapshot-consuming features\n * (e.g. agent context capture, share / export flows).\n */\n public override GetCurrentStateSnapshot(): DataSnapshot | null {\n if (!this.artifactVersion) {\n return null;\n }\n const snap = new DataSnapshot();\n snap.title = this.getDisplayTitle() ?? undefined;\n\n const dimensionPart =\n this.naturalWidth && this.naturalHeight ? ` ${this.naturalWidth} × ${this.naturalHeight}` : '';\n const mimeLabel = this.artifactVersion.MimeType ?? 'image';\n snap.interpretation = `Image (${mimeLabel}${dimensionPart}).`;\n\n snap.custom = {\n mimeType: this.artifactVersion.MimeType ?? undefined,\n fileName: this.artifactVersion.FileName ?? undefined,\n contentMode: this.artifactVersion.ContentMode,\n width: this.naturalWidth ?? undefined,\n height: this.naturalHeight ?? undefined,\n displayMode: this.displayMode,\n };\n return snap;\n }\n}\n"]}
@@ -298,7 +298,7 @@ let PdfArtifactViewerComponent = class PdfArtifactViewerComponent extends BaseAr
298
298
  i0.ɵɵelementEnd()();
299
299
  } if (rf & 2) {
300
300
  i0.ɵɵadvance();
301
- 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);
301
+ i0.ɵɵproperty("fileName", 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);
302
302
  i0.ɵɵadvance(2);
303
303
  i0.ɵɵconditional(ctx.isLoading ? 3 : ctx.errorMessage ? 4 : 5);
304
304
  } }, 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 }"] });
@@ -312,7 +312,7 @@ export { PdfArtifactViewerComponent };
312
312
  args: [{ standalone: false, selector: 'mj-pdf-artifact-viewer', template: `
313
313
  <div class="pdf-viewer">
314
314
  <mj-file-artifact-toolbar
315
- [fileName]="artifactVersion?.FileName || 'document.pdf'"
315
+ [fileName]="artifactVersion.FileName || 'document.pdf'"
316
316
  [currentPage]="currentPage"
317
317
  [totalPages]="totalPages"
318
318
  [isDownloading]="isDownloading"
@@ -1 +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 { DataSnapshot } from '@memberjunction/core';\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 >\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 `\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})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'PdfArtifactViewerPlugin')\nexport class PdfArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit, OnDestroy {\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 {\n return true;\n }\n\n /** Current zoom as an integer percentage for toolbar display. */\n public get zoomPercent(): number {\n return Math.round(this.zoomLevel * 100);\n }\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(content, this.artifactVersion.MimeType || 'application/pdf');\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(this.ZOOM_STEPS[0], Math.min(fitZoom, this.ZOOM_STEPS[this.ZOOM_STEPS.length - 1]));\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 public override GetCurrentStateSnapshot(): DataSnapshot | null {\n const snap = new DataSnapshot();\n snap.title = this.getDisplayTitle() ?? undefined;\n snap.interpretation = `PDF document, ${this.totalPages} page${this.totalPages !== 1 ? 's' : ''}. Currently viewing page ${this.currentPage}.`;\n snap.custom = {\n pageCount: this.totalPages,\n currentPage: this.currentPage,\n fileName: this.artifactVersion.FileName ?? undefined,\n mimeType: this.artifactVersion.MimeType ?? 'application/pdf',\n contentMode: this.artifactVersion.ContentMode,\n };\n return snap;\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"]}
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 { DataSnapshot } from '@memberjunction/core';\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 >\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 `\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})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'PdfArtifactViewerPlugin')\nexport class PdfArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit, OnDestroy {\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 {\n return true;\n }\n\n /** Current zoom as an integer percentage for toolbar display. */\n public get zoomPercent(): number {\n return Math.round(this.zoomLevel * 100);\n }\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(content, this.artifactVersion.MimeType || 'application/pdf');\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(this.ZOOM_STEPS[0], Math.min(fitZoom, this.ZOOM_STEPS[this.ZOOM_STEPS.length - 1]));\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 public override GetCurrentStateSnapshot(): DataSnapshot | null {\n const snap = new DataSnapshot();\n snap.title = this.getDisplayTitle() ?? undefined;\n snap.interpretation = `PDF document, ${this.totalPages} page${this.totalPages !== 1 ? 's' : ''}. Currently viewing page ${this.currentPage}.`;\n snap.custom = {\n pageCount: this.totalPages,\n currentPage: this.currentPage,\n fileName: this.artifactVersion.FileName ?? undefined,\n mimeType: this.artifactVersion.MimeType ?? 'application/pdf',\n contentMode: this.artifactVersion.ContentMode,\n };\n return snap;\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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"save-query-dialog.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/save-query-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAsC,iBAAiB,EAAgB,MAAM,eAAe,CAAC;AAC5I,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAY,YAAY,EAA4B,MAAM,sBAAsB,CAAC;AAGxF,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;;AAEjE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,qBAwca,uBAAwB,SAAQ,oBAAoB;IAuCnD,OAAO,CAAC,GAAG;IAtCd,SAAS,SAAM;IACf,gBAAgB,SAAM;IACtB,GAAG,SAAM;IACT,IAAI,SAAM;IAET,KAAK,gCAAuC;IAC5C,SAAS,qBAA4B;IAEpB,YAAY,EAAG,qBAAqB,CAAC;IAGzD,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,QAAQ,UAAS;IACjB,YAAY,SAAM;IAClB,SAAS,UAAS;IAGzB,OAAO,CAAC,kBAAkB,CAAuB;IAC1C,mBAAmB,EAAE,YAAY,GAAG,IAAI,CAAQ;IAGhD,kBAAkB,UAAS;IAC3B,wBAAwB,UAAS;IACjC,eAAe,SAAM;IACrB,mBAAmB,SAAM;IAChC,OAAO,CAAC,mBAAmB,CAAuB;IAElD,+CAA+C;IACxC,oBAAoB,EAAE,gBAAgB,CAO3C;gBAEkB,GAAG,EAAE,iBAAiB;IAG1C,IAAW,OAAO,IAAI,OAAO,CAE5B;IAED,QAAQ,IAAI,IAAI;IAaT,WAAW,IAAI,IAAI;IAYnB,iBAAiB,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI;IAchE,mBAAmB,IAAI,IAAI;IAQ3B,oBAAoB,IAAI,IAAI;IAQ5B,0BAA0B,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI;IAUnE,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA6D/B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAuD7B,QAAQ,IAAI,IAAI;IAWvB,OAAO,CAAC,qBAAqB;yCA9OlB,uBAAuB;2CAAvB,uBAAuB;CAqPnC"}
1
+ {"version":3,"file":"save-query-dialog.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/save-query-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAsC,iBAAiB,EAAgB,MAAM,eAAe,CAAC;AAC5I,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAY,YAAY,EAA4B,MAAM,sBAAsB,CAAC;AAGxF,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;;AAEjE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,qBAwca,uBAAwB,SAAQ,oBAAoB;IAuCnD,OAAO,CAAC,GAAG;IAtCd,SAAS,SAAM;IACf,gBAAgB,SAAM;IACtB,GAAG,SAAM;IACT,IAAI,SAAM;IAET,KAAK,gCAAuC;IAC5C,SAAS,qBAA4B;IAEpB,YAAY,EAAG,qBAAqB,CAAC;IAGzD,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,QAAQ,UAAS;IACjB,YAAY,SAAM;IAClB,SAAS,UAAS;IAGzB,OAAO,CAAC,kBAAkB,CAAuB;IAC1C,mBAAmB,EAAE,YAAY,GAAG,IAAI,CAAQ;IAGhD,kBAAkB,UAAS;IAC3B,wBAAwB,UAAS;IACjC,eAAe,SAAM;IACrB,mBAAmB,SAAM;IAChC,OAAO,CAAC,mBAAmB,CAAuB;IAElD,+CAA+C;IACxC,oBAAoB,EAAE,gBAAgB,CAO3C;gBAEkB,GAAG,EAAE,iBAAiB;IAG1C,IAAW,OAAO,IAAI,OAAO,CAE5B;IAED,QAAQ,IAAI,IAAI;IAaT,WAAW,IAAI,IAAI;IAYnB,iBAAiB,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI;IAchE,mBAAmB,IAAI,IAAI;IAQ3B,oBAAoB,IAAI,IAAI;IAQ5B,0BAA0B,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI;IAUnE,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA0D/B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAuD7B,QAAQ,IAAI,IAAI;IAWvB,OAAO,CAAC,qBAAqB;yCA3OlB,uBAAuB;2CAAvB,uBAAuB;CAkPnC"}
@@ -217,8 +217,6 @@ export class SaveQueryPanelComponent extends BaseAngularComponent {
217
217
  const saved = await category.Save();
218
218
  if (saved) {
219
219
  MJNotificationService.Instance.CreateSimpleNotification(`Category "${name}" created`, 'success', 2500);
220
- // Refresh core metadata so Metadata.QueryCategories is up to date
221
- await md.Refresh();
222
220
  // Refresh tree to pick up the new category
223
221
  await this.categoryTree.Refresh();
224
222
  // Auto-select the newly created category.
@@ -1 +1 @@
1
- {"version":3,"file":"save-query-dialog.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/save-query-dialog.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, HostListener } from '@angular/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { Metadata, CompositeKey, KeyValuePair, BaseEntity } from '@memberjunction/core';\nimport { MJQueryEntity, MJQueryCategoryEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { TreeBranchConfig, TreeNode } from '@memberjunction/ng-trees';\nimport { TreeDropdownComponent } from '@memberjunction/ng-trees';\n\n/**\n * Result emitted when a query is successfully saved\n */\nexport interface SaveQueryResult {\n queryId: string;\n queryName: string;\n}\n\n/**\n * Slide-in panel for saving an ad-hoc SQL query as a reusable MJ Query record.\n * Pre-populates name from the artifact title and provides a tree dropdown\n * for selecting a query category, with inline category creation.\n *\n * Follows the CreateAgentSlideInComponent pattern for slide-in UX.\n */\n@Component({\n standalone: false,\n selector: 'mj-save-query-panel',\n template: `\n <!-- Backdrop -->\n <div class=\"sqp-backdrop\" [class.sqp-visible]=\"IsVisible\" (click)=\"OnCancel()\"></div>\n\n <!-- Slide panel -->\n <div class=\"sqp-panel\" [class.sqp-visible]=\"IsVisible\">\n <!-- Header -->\n <div class=\"sqp-header\">\n <div class=\"sqp-title-group\">\n <i class=\"fa-solid fa-save sqp-title-icon\"></i>\n <h2 class=\"sqp-title\">Save Query</h2>\n </div>\n <button class=\"sqp-close-btn\" (click)=\"OnCancel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"sqp-body\">\n <!-- Name -->\n <div class=\"sqp-field\">\n <label class=\"sqp-label\">\n Name <span class=\"sqp-required\">*</span>\n </label>\n <input\n type=\"text\"\n class=\"sqp-input\"\n [(ngModel)]=\"Name\"\n placeholder=\"Query name\"\n (keydown.enter)=\"OnSave()\">\n </div>\n\n <!-- Category -->\n <div class=\"sqp-field\">\n <div class=\"sqp-label-row\">\n <label class=\"sqp-label\">Category</label>\n @if (!IsCreatingCategory) {\n <button class=\"sqp-add-btn\" (click)=\"StartCreateCategory()\" title=\"Create new category\">\n <i class=\"fa-solid fa-plus\"></i> New\n </button>\n }\n </div>\n\n <mj-tree-dropdown\n #categoryTree\n [BranchConfig]=\"CategoryBranchConfig\"\n [SelectionMode]=\"'single'\"\n [SelectableTypes]=\"'branch'\"\n [Placeholder]=\"'Select a category (optional)'\"\n [Value]=\"SelectedCategoryKey\"\n (SelectionChange)=\"OnCategoryChanged($event)\">\n </mj-tree-dropdown>\n\n <!-- Inline create category form -->\n @if (IsCreatingCategory) {\n <div class=\"sqp-create-category\">\n <div class=\"sqp-create-header\">\n <i class=\"fa-solid fa-folder-plus\"></i>\n <span>New Category</span>\n </div>\n <div class=\"sqp-create-fields\">\n <input\n type=\"text\"\n class=\"sqp-input\"\n [(ngModel)]=\"NewCategoryName\"\n placeholder=\"Category name\"\n (keydown.enter)=\"CreateCategory()\"\n (keydown.escape)=\"CancelCreateCategory()\">\n <div class=\"sqp-create-parent\">\n <label class=\"sqp-sublabel\">Parent (optional)</label>\n <mj-tree-dropdown\n [BranchConfig]=\"CategoryBranchConfig\"\n [SelectionMode]=\"'single'\"\n [SelectableTypes]=\"'branch'\"\n [Placeholder]=\"'None (top level)'\"\n (SelectionChange)=\"OnNewCategoryParentChanged($event)\">\n </mj-tree-dropdown>\n </div>\n </div>\n <div class=\"sqp-create-actions\">\n <button class=\"sqp-btn sqp-btn-create\"\n (click)=\"CreateCategory()\"\n [disabled]=\"!NewCategoryName.trim() || IsCreatingCategorySaving\">\n @if (IsCreatingCategorySaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-check\"></i>\n }\n {{ IsCreatingCategorySaving ? 'Creating...' : 'Create' }}\n </button>\n <button class=\"sqp-btn sqp-btn-cancel-create\"\n (click)=\"CancelCreateCategory()\"\n [disabled]=\"IsCreatingCategorySaving\">\n Cancel\n </button>\n </div>\n @if (CreateCategoryError) {\n <div class=\"sqp-inline-error\">{{ CreateCategoryError }}</div>\n }\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"sqp-field\">\n <label class=\"sqp-label\">Description</label>\n <textarea\n class=\"sqp-textarea\"\n [(ngModel)]=\"Description\"\n placeholder=\"Optional description\"\n rows=\"3\">\n </textarea>\n </div>\n\n @if (ErrorMessage) {\n <div class=\"sqp-error\">\n <i class=\"fa-solid fa-exclamation-circle\"></i>\n {{ ErrorMessage }}\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"sqp-footer\">\n <button class=\"sqp-btn sqp-btn-save\"\n (click)=\"OnSave()\"\n [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Saving...\n } @else {\n <i class=\"fa-solid fa-save\"></i> Save Query\n }\n </button>\n <button class=\"sqp-btn sqp-btn-cancel\"\n (click)=\"OnCancel()\"\n [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n `,\n styles: [`\n /* Backdrop */\n .sqp-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible {\n background: var(--mj-bg-overlay, rgba(0, 0, 0, 0.3));\n pointer-events: auto;\n }\n\n /* Panel — uses right offset instead of transform to avoid creating\n a new containing block that breaks position:fixed children\n (e.g., the tree-dropdown popup). */\n .sqp-panel {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--mj-bg-surface);\n box-shadow: -8px 0 32px rgba(0, 0, 0, 0.12);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n\n /* Header */\n .sqp-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n .sqp-title-group {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .sqp-title-icon {\n font-size: 20px;\n color: var(--mj-brand-primary);\n }\n .sqp-title {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n .sqp-close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n border-radius: 8px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 16px;\n flex-shrink: 0;\n }\n .sqp-close-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n /* Body */\n .sqp-body {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n }\n\n /* Fields */\n .sqp-field {\n margin-bottom: 20px;\n }\n .sqp-label-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .sqp-label {\n display: block;\n margin-bottom: 6px;\n font-weight: 600;\n font-size: 13px;\n color: var(--mj-text-primary);\n }\n .sqp-label-row .sqp-label {\n margin-bottom: 0;\n }\n .sqp-sublabel {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n .sqp-required {\n color: var(--mj-status-error);\n }\n .sqp-input {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-input:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-input::placeholder {\n color: var(--mj-text-disabled);\n }\n .sqp-textarea {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n resize: vertical;\n font-family: inherit;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-textarea:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-textarea::placeholder {\n color: var(--mj-text-disabled);\n }\n\n /* Add category button */\n .sqp-add-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n background: none;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 12px;\n color: var(--mj-brand-primary);\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-add-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n }\n\n /* Inline create category */\n .sqp-create-category {\n margin-top: 12px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n }\n .sqp-create-header {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n margin-bottom: 10px;\n }\n .sqp-create-header i {\n color: var(--mj-brand-primary);\n }\n .sqp-create-fields {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .sqp-create-parent {\n margin-top: 2px;\n }\n .sqp-create-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n .sqp-inline-error {\n margin-top: 8px;\n font-size: 12px;\n color: var(--mj-status-error-text);\n }\n\n /* Buttons */\n .sqp-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n .sqp-btn-save {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-save:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n }\n .sqp-btn-cancel {\n background: none;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-cancel:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .sqp-btn-create {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n }\n .sqp-btn-create:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 85%, black);\n }\n .sqp-btn-cancel-create {\n background: none;\n color: var(--mj-text-secondary);\n }\n .sqp-btn-cancel-create:hover:not(:disabled) {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-sunken);\n }\n\n /* Footer */\n .sqp-footer {\n display: flex;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n /* Error */\n .sqp-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 6px;\n color: var(--mj-status-error-text);\n font-size: 13px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .sqp-panel {\n width: 100% !important;\n right: -100%;\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n .sqp-header {\n padding: 16px 20px 12px;\n }\n .sqp-body {\n padding: 16px;\n }\n .sqp-footer {\n padding: 12px 16px;\n }\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SaveQueryPanelComponent extends BaseAngularComponent {\n @Input() QueryName = '';\n @Input() QueryDescription = '';\n @Input() SQL = '';\n @Input() Plan = '';\n\n @Output() Saved = new EventEmitter<SaveQueryResult>();\n @Output() Cancelled = new EventEmitter<void>();\n\n @ViewChild('categoryTree') categoryTree!: TreeDropdownComponent;\n\n // Form state\n public Name = '';\n public Description = '';\n public IsSaving = false;\n public ErrorMessage = '';\n public IsVisible = false;\n\n // Category selection\n private selectedCategoryId: string | null = null;\n public SelectedCategoryKey: CompositeKey | null = null;\n\n // Inline category creation\n public IsCreatingCategory = false;\n public IsCreatingCategorySaving = false;\n public NewCategoryName = '';\n public CreateCategoryError = '';\n private newCategoryParentId: string | null = null;\n\n /** Tree config for query category hierarchy */\n public CategoryBranchConfig: TreeBranchConfig = {\n EntityName: 'MJ: Query Categories',\n DisplayField: 'Name',\n IDField: 'ID',\n ParentIDField: 'ParentID',\n DefaultIcon: 'fa-solid fa-folder',\n OrderBy: 'Name ASC'\n };\n\n constructor(private cdr: ChangeDetectorRef) {\n super();}\n\n public get CanSave(): boolean {\n return !!this.Name?.trim();\n }\n\n ngOnInit(): void {\n // Sync input values\n this.Name = this.QueryName;\n this.Description = this.QueryDescription;\n\n // Trigger slide-in on next microtask (ensures DOM is ready)\n Promise.resolve().then(() => {\n this.IsVisible = true;\n this.cdr.markForCheck();\n });\n }\n\n @HostListener('document:keydown.escape')\n public OnEscapeKey(): void {\n if (this.IsCreatingCategory) {\n this.CancelCreateCategory();\n } else if (!this.IsSaving) {\n this.OnCancel();\n }\n }\n\n // =========================================================================\n // Category Selection\n // =========================================================================\n\n public OnCategoryChanged(selection: TreeNode | TreeNode[] | null): void {\n if (!selection) {\n this.selectedCategoryId = null;\n } else if (Array.isArray(selection)) {\n this.selectedCategoryId = selection.length > 0 ? selection[0].ID : null;\n } else {\n this.selectedCategoryId = selection.ID;\n }\n }\n\n // =========================================================================\n // Inline Category Creation\n // =========================================================================\n\n public StartCreateCategory(): void {\n this.IsCreatingCategory = true;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n }\n\n public CancelCreateCategory(): void {\n this.IsCreatingCategory = false;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n }\n\n public OnNewCategoryParentChanged(selection: TreeNode | TreeNode[] | null): void {\n if (!selection) {\n this.newCategoryParentId = null;\n } else if (Array.isArray(selection)) {\n this.newCategoryParentId = selection.length > 0 ? selection[0].ID : null;\n } else {\n this.newCategoryParentId = selection.ID;\n }\n }\n\n public async CreateCategory(): Promise<void> {\n const name = this.NewCategoryName.trim();\n if (!name || this.IsCreatingCategorySaving) return;\n\n this.IsCreatingCategorySaving = true;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n\n try {\n const md = this.ProviderToUse;\n const category = await md.GetEntityObject<MJQueryCategoryEntity>('MJ: Query Categories');\n\n category.Name = name;\n category.UserID = md.CurrentUser.ID;\n if (this.newCategoryParentId) {\n category.ParentID = this.newCategoryParentId;\n }\n\n const saved = await category.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(`Category \"${name}\" created`, 'success', 2500);\n\n // Refresh core metadata so Metadata.QueryCategories is up to date\n await md.Refresh();\n\n // Refresh tree to pick up the new category\n await this.categoryTree.Refresh();\n\n // Auto-select the newly created category.\n // Must use detectChanges() (not markForCheck) to immediately propagate\n // the new Value input to the tree dropdown, since this panel uses OnPush.\n const newKey = new CompositeKey([new KeyValuePair('ID', category.ID)]);\n this.SelectedCategoryKey = newKey;\n this.selectedCategoryId = category.ID;\n this.cdr.detectChanges();\n\n // Collapse the create form\n this.IsCreatingCategory = false;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n } else {\n const errorMsg = this.getEntityErrorMessage(category, 'create category');\n this.CreateCategoryError = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Failed to create category:', category.LatestResult);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'Failed to create category';\n this.CreateCategoryError = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Error creating category:', error);\n } finally {\n this.IsCreatingCategorySaving = false;\n this.cdr.markForCheck();\n }\n }\n\n // =========================================================================\n // Save / Cancel\n // =========================================================================\n\n public async OnSave(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.ErrorMessage = '';\n this.cdr.markForCheck();\n\n try {\n const md = this.ProviderToUse;\n const query = await md.GetEntityObject<MJQueryEntity>('MJ: Queries');\n\n query.Name = this.Name.trim();\n query.SQL = this.SQL;\n query.Status = 'Approved';\n\n if (this.Description?.trim()) {\n query.Description = this.Description.trim();\n }\n if (this.Plan?.trim()) {\n query.TechnicalDescription = this.Plan.trim();\n }\n if (this.selectedCategoryId) {\n query.CategoryID = this.selectedCategoryId;\n }\n\n const saved = await query.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(`Query \"${query.Name}\" saved`, 'success', 2500);\n\n // Animate out, then emit\n this.IsVisible = false;\n this.cdr.markForCheck();\n setTimeout(() => {\n this.Saved.emit({\n queryId: query.ID,\n queryName: query.Name\n });\n }, 300);\n } else {\n const errorMsg = this.getEntityErrorMessage(query, 'save query');\n this.ErrorMessage = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Failed to save query:', query.LatestResult);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'An unexpected error occurred';\n this.ErrorMessage = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Error saving query:', error);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public OnCancel(): void {\n if (this.IsSaving) return;\n this.IsVisible = false;\n this.cdr.markForCheck();\n setTimeout(() => this.Cancelled.emit(), 300);\n }\n\n // =========================================================================\n // Helpers\n // =========================================================================\n\n private getEntityErrorMessage(entity: BaseEntity, operation: string): string {\n const result = entity.LatestResult;\n if (result?.Message) {\n return result.Message;\n }\n return `Failed to ${operation}. Please try again.`;\n }\n}\n"]}
1
+ {"version":3,"file":"save-query-dialog.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/save-query-dialog.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, HostListener } from '@angular/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { Metadata, CompositeKey, KeyValuePair, BaseEntity } from '@memberjunction/core';\nimport { MJQueryEntity, MJQueryCategoryEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { TreeBranchConfig, TreeNode } from '@memberjunction/ng-trees';\nimport { TreeDropdownComponent } from '@memberjunction/ng-trees';\n\n/**\n * Result emitted when a query is successfully saved\n */\nexport interface SaveQueryResult {\n queryId: string;\n queryName: string;\n}\n\n/**\n * Slide-in panel for saving an ad-hoc SQL query as a reusable MJ Query record.\n * Pre-populates name from the artifact title and provides a tree dropdown\n * for selecting a query category, with inline category creation.\n *\n * Follows the CreateAgentSlideInComponent pattern for slide-in UX.\n */\n@Component({\n standalone: false,\n selector: 'mj-save-query-panel',\n template: `\n <!-- Backdrop -->\n <div class=\"sqp-backdrop\" [class.sqp-visible]=\"IsVisible\" (click)=\"OnCancel()\"></div>\n\n <!-- Slide panel -->\n <div class=\"sqp-panel\" [class.sqp-visible]=\"IsVisible\">\n <!-- Header -->\n <div class=\"sqp-header\">\n <div class=\"sqp-title-group\">\n <i class=\"fa-solid fa-save sqp-title-icon\"></i>\n <h2 class=\"sqp-title\">Save Query</h2>\n </div>\n <button class=\"sqp-close-btn\" (click)=\"OnCancel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"sqp-body\">\n <!-- Name -->\n <div class=\"sqp-field\">\n <label class=\"sqp-label\">\n Name <span class=\"sqp-required\">*</span>\n </label>\n <input\n type=\"text\"\n class=\"sqp-input\"\n [(ngModel)]=\"Name\"\n placeholder=\"Query name\"\n (keydown.enter)=\"OnSave()\">\n </div>\n\n <!-- Category -->\n <div class=\"sqp-field\">\n <div class=\"sqp-label-row\">\n <label class=\"sqp-label\">Category</label>\n @if (!IsCreatingCategory) {\n <button class=\"sqp-add-btn\" (click)=\"StartCreateCategory()\" title=\"Create new category\">\n <i class=\"fa-solid fa-plus\"></i> New\n </button>\n }\n </div>\n\n <mj-tree-dropdown\n #categoryTree\n [BranchConfig]=\"CategoryBranchConfig\"\n [SelectionMode]=\"'single'\"\n [SelectableTypes]=\"'branch'\"\n [Placeholder]=\"'Select a category (optional)'\"\n [Value]=\"SelectedCategoryKey\"\n (SelectionChange)=\"OnCategoryChanged($event)\">\n </mj-tree-dropdown>\n\n <!-- Inline create category form -->\n @if (IsCreatingCategory) {\n <div class=\"sqp-create-category\">\n <div class=\"sqp-create-header\">\n <i class=\"fa-solid fa-folder-plus\"></i>\n <span>New Category</span>\n </div>\n <div class=\"sqp-create-fields\">\n <input\n type=\"text\"\n class=\"sqp-input\"\n [(ngModel)]=\"NewCategoryName\"\n placeholder=\"Category name\"\n (keydown.enter)=\"CreateCategory()\"\n (keydown.escape)=\"CancelCreateCategory()\">\n <div class=\"sqp-create-parent\">\n <label class=\"sqp-sublabel\">Parent (optional)</label>\n <mj-tree-dropdown\n [BranchConfig]=\"CategoryBranchConfig\"\n [SelectionMode]=\"'single'\"\n [SelectableTypes]=\"'branch'\"\n [Placeholder]=\"'None (top level)'\"\n (SelectionChange)=\"OnNewCategoryParentChanged($event)\">\n </mj-tree-dropdown>\n </div>\n </div>\n <div class=\"sqp-create-actions\">\n <button class=\"sqp-btn sqp-btn-create\"\n (click)=\"CreateCategory()\"\n [disabled]=\"!NewCategoryName.trim() || IsCreatingCategorySaving\">\n @if (IsCreatingCategorySaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-check\"></i>\n }\n {{ IsCreatingCategorySaving ? 'Creating...' : 'Create' }}\n </button>\n <button class=\"sqp-btn sqp-btn-cancel-create\"\n (click)=\"CancelCreateCategory()\"\n [disabled]=\"IsCreatingCategorySaving\">\n Cancel\n </button>\n </div>\n @if (CreateCategoryError) {\n <div class=\"sqp-inline-error\">{{ CreateCategoryError }}</div>\n }\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"sqp-field\">\n <label class=\"sqp-label\">Description</label>\n <textarea\n class=\"sqp-textarea\"\n [(ngModel)]=\"Description\"\n placeholder=\"Optional description\"\n rows=\"3\">\n </textarea>\n </div>\n\n @if (ErrorMessage) {\n <div class=\"sqp-error\">\n <i class=\"fa-solid fa-exclamation-circle\"></i>\n {{ ErrorMessage }}\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"sqp-footer\">\n <button class=\"sqp-btn sqp-btn-save\"\n (click)=\"OnSave()\"\n [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Saving...\n } @else {\n <i class=\"fa-solid fa-save\"></i> Save Query\n }\n </button>\n <button class=\"sqp-btn sqp-btn-cancel\"\n (click)=\"OnCancel()\"\n [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n `,\n styles: [`\n /* Backdrop */\n .sqp-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible {\n background: var(--mj-bg-overlay, rgba(0, 0, 0, 0.3));\n pointer-events: auto;\n }\n\n /* Panel — uses right offset instead of transform to avoid creating\n a new containing block that breaks position:fixed children\n (e.g., the tree-dropdown popup). */\n .sqp-panel {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--mj-bg-surface);\n box-shadow: -8px 0 32px rgba(0, 0, 0, 0.12);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n\n /* Header */\n .sqp-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n .sqp-title-group {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .sqp-title-icon {\n font-size: 20px;\n color: var(--mj-brand-primary);\n }\n .sqp-title {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n .sqp-close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n border-radius: 8px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 16px;\n flex-shrink: 0;\n }\n .sqp-close-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n /* Body */\n .sqp-body {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n }\n\n /* Fields */\n .sqp-field {\n margin-bottom: 20px;\n }\n .sqp-label-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .sqp-label {\n display: block;\n margin-bottom: 6px;\n font-weight: 600;\n font-size: 13px;\n color: var(--mj-text-primary);\n }\n .sqp-label-row .sqp-label {\n margin-bottom: 0;\n }\n .sqp-sublabel {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n .sqp-required {\n color: var(--mj-status-error);\n }\n .sqp-input {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-input:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-input::placeholder {\n color: var(--mj-text-disabled);\n }\n .sqp-textarea {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n resize: vertical;\n font-family: inherit;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-textarea:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-textarea::placeholder {\n color: var(--mj-text-disabled);\n }\n\n /* Add category button */\n .sqp-add-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n background: none;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 12px;\n color: var(--mj-brand-primary);\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-add-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n }\n\n /* Inline create category */\n .sqp-create-category {\n margin-top: 12px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n }\n .sqp-create-header {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n margin-bottom: 10px;\n }\n .sqp-create-header i {\n color: var(--mj-brand-primary);\n }\n .sqp-create-fields {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .sqp-create-parent {\n margin-top: 2px;\n }\n .sqp-create-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n .sqp-inline-error {\n margin-top: 8px;\n font-size: 12px;\n color: var(--mj-status-error-text);\n }\n\n /* Buttons */\n .sqp-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n .sqp-btn-save {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-save:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n }\n .sqp-btn-cancel {\n background: none;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-cancel:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .sqp-btn-create {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n }\n .sqp-btn-create:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 85%, black);\n }\n .sqp-btn-cancel-create {\n background: none;\n color: var(--mj-text-secondary);\n }\n .sqp-btn-cancel-create:hover:not(:disabled) {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-sunken);\n }\n\n /* Footer */\n .sqp-footer {\n display: flex;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n /* Error */\n .sqp-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 6px;\n color: var(--mj-status-error-text);\n font-size: 13px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .sqp-panel {\n width: 100% !important;\n right: -100%;\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n .sqp-header {\n padding: 16px 20px 12px;\n }\n .sqp-body {\n padding: 16px;\n }\n .sqp-footer {\n padding: 12px 16px;\n }\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SaveQueryPanelComponent extends BaseAngularComponent {\n @Input() QueryName = '';\n @Input() QueryDescription = '';\n @Input() SQL = '';\n @Input() Plan = '';\n\n @Output() Saved = new EventEmitter<SaveQueryResult>();\n @Output() Cancelled = new EventEmitter<void>();\n\n @ViewChild('categoryTree') categoryTree!: TreeDropdownComponent;\n\n // Form state\n public Name = '';\n public Description = '';\n public IsSaving = false;\n public ErrorMessage = '';\n public IsVisible = false;\n\n // Category selection\n private selectedCategoryId: string | null = null;\n public SelectedCategoryKey: CompositeKey | null = null;\n\n // Inline category creation\n public IsCreatingCategory = false;\n public IsCreatingCategorySaving = false;\n public NewCategoryName = '';\n public CreateCategoryError = '';\n private newCategoryParentId: string | null = null;\n\n /** Tree config for query category hierarchy */\n public CategoryBranchConfig: TreeBranchConfig = {\n EntityName: 'MJ: Query Categories',\n DisplayField: 'Name',\n IDField: 'ID',\n ParentIDField: 'ParentID',\n DefaultIcon: 'fa-solid fa-folder',\n OrderBy: 'Name ASC'\n };\n\n constructor(private cdr: ChangeDetectorRef) {\n super();}\n\n public get CanSave(): boolean {\n return !!this.Name?.trim();\n }\n\n ngOnInit(): void {\n // Sync input values\n this.Name = this.QueryName;\n this.Description = this.QueryDescription;\n\n // Trigger slide-in on next microtask (ensures DOM is ready)\n Promise.resolve().then(() => {\n this.IsVisible = true;\n this.cdr.markForCheck();\n });\n }\n\n @HostListener('document:keydown.escape')\n public OnEscapeKey(): void {\n if (this.IsCreatingCategory) {\n this.CancelCreateCategory();\n } else if (!this.IsSaving) {\n this.OnCancel();\n }\n }\n\n // =========================================================================\n // Category Selection\n // =========================================================================\n\n public OnCategoryChanged(selection: TreeNode | TreeNode[] | null): void {\n if (!selection) {\n this.selectedCategoryId = null;\n } else if (Array.isArray(selection)) {\n this.selectedCategoryId = selection.length > 0 ? selection[0].ID : null;\n } else {\n this.selectedCategoryId = selection.ID;\n }\n }\n\n // =========================================================================\n // Inline Category Creation\n // =========================================================================\n\n public StartCreateCategory(): void {\n this.IsCreatingCategory = true;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n }\n\n public CancelCreateCategory(): void {\n this.IsCreatingCategory = false;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n }\n\n public OnNewCategoryParentChanged(selection: TreeNode | TreeNode[] | null): void {\n if (!selection) {\n this.newCategoryParentId = null;\n } else if (Array.isArray(selection)) {\n this.newCategoryParentId = selection.length > 0 ? selection[0].ID : null;\n } else {\n this.newCategoryParentId = selection.ID;\n }\n }\n\n public async CreateCategory(): Promise<void> {\n const name = this.NewCategoryName.trim();\n if (!name || this.IsCreatingCategorySaving) return;\n\n this.IsCreatingCategorySaving = true;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n\n try {\n const md = this.ProviderToUse;\n const category = await md.GetEntityObject<MJQueryCategoryEntity>('MJ: Query Categories');\n\n category.Name = name;\n category.UserID = md.CurrentUser.ID;\n if (this.newCategoryParentId) {\n category.ParentID = this.newCategoryParentId;\n }\n\n const saved = await category.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(`Category \"${name}\" created`, 'success', 2500);\n\n // Refresh tree to pick up the new category\n await this.categoryTree.Refresh();\n\n // Auto-select the newly created category.\n // Must use detectChanges() (not markForCheck) to immediately propagate\n // the new Value input to the tree dropdown, since this panel uses OnPush.\n const newKey = new CompositeKey([new KeyValuePair('ID', category.ID)]);\n this.SelectedCategoryKey = newKey;\n this.selectedCategoryId = category.ID;\n this.cdr.detectChanges();\n\n // Collapse the create form\n this.IsCreatingCategory = false;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n } else {\n const errorMsg = this.getEntityErrorMessage(category, 'create category');\n this.CreateCategoryError = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Failed to create category:', category.LatestResult);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'Failed to create category';\n this.CreateCategoryError = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Error creating category:', error);\n } finally {\n this.IsCreatingCategorySaving = false;\n this.cdr.markForCheck();\n }\n }\n\n // =========================================================================\n // Save / Cancel\n // =========================================================================\n\n public async OnSave(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.ErrorMessage = '';\n this.cdr.markForCheck();\n\n try {\n const md = this.ProviderToUse;\n const query = await md.GetEntityObject<MJQueryEntity>('MJ: Queries');\n\n query.Name = this.Name.trim();\n query.SQL = this.SQL;\n query.Status = 'Approved';\n\n if (this.Description?.trim()) {\n query.Description = this.Description.trim();\n }\n if (this.Plan?.trim()) {\n query.TechnicalDescription = this.Plan.trim();\n }\n if (this.selectedCategoryId) {\n query.CategoryID = this.selectedCategoryId;\n }\n\n const saved = await query.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(`Query \"${query.Name}\" saved`, 'success', 2500);\n\n // Animate out, then emit\n this.IsVisible = false;\n this.cdr.markForCheck();\n setTimeout(() => {\n this.Saved.emit({\n queryId: query.ID,\n queryName: query.Name\n });\n }, 300);\n } else {\n const errorMsg = this.getEntityErrorMessage(query, 'save query');\n this.ErrorMessage = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Failed to save query:', query.LatestResult);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'An unexpected error occurred';\n this.ErrorMessage = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Error saving query:', error);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public OnCancel(): void {\n if (this.IsSaving) return;\n this.IsVisible = false;\n this.cdr.markForCheck();\n setTimeout(() => this.Cancelled.emit(), 300);\n }\n\n // =========================================================================\n // Helpers\n // =========================================================================\n\n private getEntityErrorMessage(entity: BaseEntity, operation: string): string {\n const result = entity.LatestResult;\n if (result?.Message) {\n return result.Message;\n }\n return `Failed to ${operation}. Please try again.`;\n }\n}\n"]}
@@ -217,7 +217,7 @@ let XlsxArtifactViewerComponent = class XlsxArtifactViewerComponent extends Base
217
217
  i0.ɵɵelementEnd();
218
218
  } if (rf & 2) {
219
219
  i0.ɵɵadvance();
220
- i0.ɵɵproperty("fileName", (ctx.artifactVersion == null ? null : ctx.artifactVersion.FileName) || "workbook.xlsx")("isDownloading", ctx.isDownloading)("showPrint", false);
220
+ i0.ɵɵproperty("fileName", ctx.artifactVersion.FileName || "workbook.xlsx")("isDownloading", ctx.isDownloading)("showPrint", false);
221
221
  i0.ɵɵadvance();
222
222
  i0.ɵɵconditional(ctx.isLoading ? 2 : ctx.errorMessage ? 3 : 4);
223
223
  } }, 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:\n background 0.1s,\n 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 }"] });
@@ -231,7 +231,7 @@ export { XlsxArtifactViewerComponent };
231
231
  args: [{ standalone: false, selector: 'mj-xlsx-artifact-viewer', template: `
232
232
  <div class="xlsx-viewer">
233
233
  <mj-file-artifact-toolbar
234
- [fileName]="artifactVersion?.FileName || 'workbook.xlsx'"
234
+ [fileName]="artifactVersion.FileName || 'workbook.xlsx'"
235
235
  [isDownloading]="isDownloading"
236
236
  [showPrint]="false"
237
237
  (download)="onDownload()"
@@ -1 +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 { DataSnapshot, DataTable, MJColumnDescriptor } from '@memberjunction/core';\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 >\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 class=\"xlsx-viewer__tab\" [class.xlsx-viewer__tab--active]=\"i === activeSheetIndex\" (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 >\n </ag-grid-angular>\n </div>\n }\n </div>\n `,\n styles: [\n `\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:\n background 0.1s,\n 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})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'XlsxArtifactViewerPlugin')\nexport class XlsxArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit {\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 {\n return true;\n }\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(this.downloadUrl, this.artifactVersion?.FileName || 'workbook.xlsx');\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 public override GetCurrentStateSnapshot(): DataSnapshot | null {\n if (this.sheets.length === 0) return null;\n\n // Convert parsed sheets into DataTables for structured snapshot\n const tables: DataTable[] = this.sheets.map((sheet) => {\n const table = new DataTable();\n table.name = sheet.name;\n table.source = 'static';\n table.columns = sheet.columnDefs\n .filter((col) => col.field)\n .map((col) => {\n const desc = new MJColumnDescriptor(col.field as string);\n desc.displayName = (col.headerName as string | undefined) ?? (col.field as string);\n desc.sqlBaseType = 'nvarchar';\n return desc;\n });\n table.rows = sheet.rowData;\n table.metadata = { rowCount: sheet.rowData.length };\n return table;\n });\n\n const snap = DataSnapshot.FromTables(tables, this.getDisplayTitle() ?? undefined);\n snap.activeTab = this.sheets[this.activeSheetIndex]?.name;\n return snap;\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"]}
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 { DataSnapshot, DataTable, MJColumnDescriptor } from '@memberjunction/core';\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 >\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 class=\"xlsx-viewer__tab\" [class.xlsx-viewer__tab--active]=\"i === activeSheetIndex\" (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 >\n </ag-grid-angular>\n </div>\n }\n </div>\n `,\n styles: [\n `\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:\n background 0.1s,\n 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})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'XlsxArtifactViewerPlugin')\nexport class XlsxArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit {\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 {\n return true;\n }\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(this.downloadUrl, this.artifactVersion?.FileName || 'workbook.xlsx');\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 public override GetCurrentStateSnapshot(): DataSnapshot | null {\n if (this.sheets.length === 0) return null;\n\n // Convert parsed sheets into DataTables for structured snapshot\n const tables: DataTable[] = this.sheets.map((sheet) => {\n const table = new DataTable();\n table.name = sheet.name;\n table.source = 'static';\n table.columns = sheet.columnDefs\n .filter((col) => col.field)\n .map((col) => {\n const desc = new MJColumnDescriptor(col.field as string);\n desc.displayName = (col.headerName as string | undefined) ?? (col.field as string);\n desc.sqlBaseType = 'nvarchar';\n return desc;\n });\n table.rows = sheet.rowData;\n table.metadata = { rowCount: sheet.rowData.length };\n return table;\n });\n\n const snap = DataSnapshot.FromTables(tables, this.getDisplayTitle() ?? undefined);\n snap.activeTab = this.sheets[this.activeSheetIndex]?.name;\n return snap;\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,46 @@
1
+ import { type IMetadataProvider } from '@memberjunction/core';
2
+ import type { ComponentSpec } from '@memberjunction/interactive-component-types';
3
+ import * as i0 from "@angular/core";
4
+ /** Result of an apply attempt — surfaced to the caller for any post-apply UI. */
5
+ export interface InteractiveFormApplyResult {
6
+ Success: boolean;
7
+ /** Mode the server ran: 'create' / 'modify-new-version' / 'modify-in-place'. */
8
+ Mode?: 'create' | 'modify-new-version' | 'modify-in-place';
9
+ /** OverrideID the user can now reference. */
10
+ OverrideID?: string;
11
+ /** ComponentID that now holds the spec. */
12
+ ComponentID?: string;
13
+ /** Version string (e.g. '1.0.0' for create, '1.1.0' for modify-new). */
14
+ Version?: string;
15
+ Message?: string;
16
+ }
17
+ export declare class InteractiveFormApplyService {
18
+ private readonly dialog;
19
+ private readonly notifications;
20
+ /**
21
+ * Confirm with the user and apply the spec. Routes to Create / Modify
22
+ * automatically based on whether an Active override already exists.
23
+ */
24
+ ConfirmAndApply(spec: ComponentSpec, entityName: string, provider?: IMetadataProvider): Promise<InteractiveFormApplyResult>;
25
+ /**
26
+ * Look up an action by name and run it. The actions module ships with
27
+ * `__CreateInteractiveForm` / `__ModifyInteractiveForm` / etc. as their
28
+ * DriverClass names but registers under the human-readable form name.
29
+ * GraphQLActionClient.RunAction() takes the action ID; we fetch it by
30
+ * name from the action metadata loaded on the provider.
31
+ */
32
+ private runActionByName;
33
+ /**
34
+ * Resolve an action ID by Name. Falls back to a direct RunView against
35
+ * the Action entity when ActionEngine isn't loaded.
36
+ */
37
+ private resolveActionIdByName;
38
+ /** Show a confirmation dialog explaining what's about to happen. */
39
+ private confirm;
40
+ private parseActiveResult;
41
+ private summarize;
42
+ private fail;
43
+ static ɵfac: i0.ɵɵFactoryDeclaration<InteractiveFormApplyService, never>;
44
+ static ɵprov: i0.ɵɵInjectableDeclaration<InteractiveFormApplyService>;
45
+ }
46
+ //# sourceMappingURL=interactive-form-apply.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-form-apply.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/interactive-form-apply.service.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAsB,KAAK,iBAAiB,EAAiB,MAAM,sBAAsB,CAAC;AAIjG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;;AAEjF,iFAAiF;AACjF,MAAM,WAAW,0BAA0B;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,IAAI,CAAC,EAAE,QAAQ,GAAG,oBAAoB,GAAG,iBAAiB,CAAC;IAC3D,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBACa,2BAA2B;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiC;IAE/D;;;OAGG;IACU,eAAe,CACxB,IAAI,EAAE,aAAa,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,iBAAiB,GAC7B,OAAO,CAAC,0BAA0B,CAAC;IAwDtC;;;;;;OAMG;YACW,eAAe;IA+B7B;;;OAGG;YACW,qBAAqB;IAmBnC,oEAAoE;YACtD,OAAO;IA6BrB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,SAAS;IAiCjB,OAAO,CAAC,IAAI;yCA3MH,2BAA2B;6CAA3B,2BAA2B;CA+MvC"}