@memberjunction/ng-artifacts 6.2.0-edge.0 → 6.2.0-edge.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/components/artifact-message-card.component.d.ts +60 -8
- package/dist/lib/components/artifact-message-card.component.d.ts.map +1 -1
- package/dist/lib/components/artifact-message-card.component.js +135 -39
- package/dist/lib/components/artifact-message-card.component.js.map +1 -1
- package/dist/lib/components/artifact-type-plugin-viewer.component.d.ts +58 -6
- package/dist/lib/components/artifact-type-plugin-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/artifact-type-plugin-viewer.component.js +107 -29
- package/dist/lib/components/artifact-type-plugin-viewer.component.js.map +1 -1
- package/dist/lib/components/artifact-version-download.d.ts +5 -3
- package/dist/lib/components/artifact-version-download.d.ts.map +1 -1
- package/dist/lib/components/artifact-version-download.js +11 -7
- package/dist/lib/components/artifact-version-download.js.map +1 -1
- package/dist/lib/components/artifact-version-history.component.d.ts +74 -8
- package/dist/lib/components/artifact-version-history.component.d.ts.map +1 -1
- package/dist/lib/components/artifact-version-history.component.js +152 -34
- package/dist/lib/components/artifact-version-history.component.js.map +1 -1
- package/dist/lib/components/artifact-viewer-panel.component.d.ts +328 -35
- package/dist/lib/components/artifact-viewer-panel.component.d.ts.map +1 -1
- package/dist/lib/components/artifact-viewer-panel.component.js +701 -213
- package/dist/lib/components/artifact-viewer-panel.component.js.map +1 -1
- package/dist/lib/components/file-artifact-toolbar.component.d.ts +112 -10
- package/dist/lib/components/file-artifact-toolbar.component.d.ts.map +1 -1
- package/dist/lib/components/file-artifact-toolbar.component.js +197 -23
- package/dist/lib/components/file-artifact-toolbar.component.js.map +1 -1
- package/dist/lib/components/plugins/audio-artifact-viewer.component.d.ts +9 -1
- package/dist/lib/components/plugins/audio-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/audio-artifact-viewer.component.js +20 -4
- package/dist/lib/components/plugins/audio-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/code-artifact-viewer.component.d.ts +23 -5
- package/dist/lib/components/plugins/code-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/code-artifact-viewer.component.js +69 -31
- package/dist/lib/components/plugins/code-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts +136 -18
- package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/component-artifact-viewer.component.js +285 -97
- package/dist/lib/components/plugins/component-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/component-feedback-panel/component-feedback-panel.component.d.ts +2 -2
- package/dist/lib/components/plugins/component-feedback-panel/component-feedback-panel.component.js +7 -7
- package/dist/lib/components/plugins/component-feedback-panel/component-feedback-panel.component.js.map +1 -1
- package/dist/lib/components/plugins/component-permission-evaluation.d.ts +2 -0
- package/dist/lib/components/plugins/component-permission-evaluation.d.ts.map +1 -1
- package/dist/lib/components/plugins/component-permission-evaluation.js +6 -2
- package/dist/lib/components/plugins/component-permission-evaluation.js.map +1 -1
- package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts +17 -6
- package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/data-artifact-viewer.component.js +32 -22
- package/dist/lib/components/plugins/data-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.d.ts +56 -4
- package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.js +142 -36
- package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/docx-artifact-viewer.component.d.ts +14 -2
- package/dist/lib/components/plugins/docx-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/docx-artifact-viewer.component.js +32 -8
- package/dist/lib/components/plugins/docx-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/html-artifact-viewer.component.d.ts +17 -3
- package/dist/lib/components/plugins/html-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/html-artifact-viewer.component.js +36 -8
- package/dist/lib/components/plugins/html-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/image-artifact-viewer.component.d.ts +41 -5
- package/dist/lib/components/plugins/image-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/image-artifact-viewer.component.js +102 -30
- package/dist/lib/components/plugins/image-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/json-artifact-viewer.component.d.ts +33 -5
- package/dist/lib/components/plugins/json-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/json-artifact-viewer.component.js +89 -33
- package/dist/lib/components/plugins/json-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/markdown-artifact-viewer.component.d.ts +12 -2
- package/dist/lib/components/plugins/markdown-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/markdown-artifact-viewer.component.js +28 -8
- package/dist/lib/components/plugins/markdown-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/ml-experiment-results-viewer.component.d.ts +4 -0
- package/dist/lib/components/plugins/ml-experiment-results-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/ml-experiment-results-viewer.component.js +14 -6
- package/dist/lib/components/plugins/ml-experiment-results-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/pdf-artifact-viewer.component.d.ts +47 -5
- package/dist/lib/components/plugins/pdf-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/pdf-artifact-viewer.component.js +129 -45
- package/dist/lib/components/plugins/pdf-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/save-query-dialog.component.d.ts +5 -1
- package/dist/lib/components/plugins/save-query-dialog.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/save-query-dialog.component.js +12 -4
- package/dist/lib/components/plugins/save-query-dialog.component.js.map +1 -1
- package/dist/lib/components/plugins/svg-artifact-viewer.component.d.ts +17 -3
- package/dist/lib/components/plugins/svg-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/svg-artifact-viewer.component.js +36 -8
- package/dist/lib/components/plugins/svg-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/video-artifact-viewer.component.d.ts +9 -1
- package/dist/lib/components/plugins/video-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/video-artifact-viewer.component.js +20 -4
- package/dist/lib/components/plugins/video-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/xlsx-artifact-viewer.component.d.ts +28 -4
- package/dist/lib/components/plugins/xlsx-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js +64 -16
- package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/previews/audio-artifact-preview.component.d.ts +9 -1
- package/dist/lib/components/previews/audio-artifact-preview.component.d.ts.map +1 -1
- package/dist/lib/components/previews/audio-artifact-preview.component.js +20 -4
- package/dist/lib/components/previews/audio-artifact-preview.component.js.map +1 -1
- package/dist/lib/components/previews/image-artifact-preview.component.d.ts +16 -2
- package/dist/lib/components/previews/image-artifact-preview.component.d.ts.map +1 -1
- package/dist/lib/components/previews/image-artifact-preview.component.js +35 -7
- package/dist/lib/components/previews/image-artifact-preview.component.js.map +1 -1
- package/dist/lib/components/previews/video-artifact-preview.component.d.ts +9 -1
- package/dist/lib/components/previews/video-artifact-preview.component.d.ts.map +1 -1
- package/dist/lib/components/previews/video-artifact-preview.component.js +20 -4
- package/dist/lib/components/previews/video-artifact-preview.component.js.map +1 -1
- package/dist/lib/interfaces/artifact-viewer-plugin.interface.js.map +1 -1
- package/dist/lib/services/artifact-file.service.d.ts +8 -0
- package/dist/lib/services/artifact-file.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-file.service.js +22 -6
- package/dist/lib/services/artifact-file.service.js.map +1 -1
- package/dist/lib/services/artifact-icon.service.d.ts +2 -0
- package/dist/lib/services/artifact-icon.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-icon.service.js +5 -1
- package/dist/lib/services/artifact-icon.service.js.map +1 -1
- package/dist/lib/services/artifact-preview-resolver.service.d.ts +2 -0
- package/dist/lib/services/artifact-preview-resolver.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-preview-resolver.service.js +5 -1
- package/dist/lib/services/artifact-preview-resolver.service.js.map +1 -1
- package/dist/lib/snapshot-helpers.d.ts +10 -0
- package/dist/lib/snapshot-helpers.d.ts.map +1 -1
- package/dist/lib/snapshot-helpers.js +25 -5
- package/dist/lib/snapshot-helpers.js.map +1 -1
- package/package.json +21 -21
|
@@ -94,7 +94,7 @@ export const IMAGE_MIME_EXTENSION_MAP = Object.freeze({
|
|
|
94
94
|
* @param mime MIME type from the artifact version (e.g. `"image/jpeg"`). Optional.
|
|
95
95
|
* @returns Sanitized filename with extension (e.g. `"Generated_image_1.jpg"`).
|
|
96
96
|
*/
|
|
97
|
-
export function
|
|
97
|
+
export function BuildImageDownloadFileName(name, mime) {
|
|
98
98
|
// `??` falls back on null/undefined but NOT empty / whitespace-only names —
|
|
99
99
|
// those should also use the default, so trim first and check for length.
|
|
100
100
|
const trimmed = name?.trim();
|
|
@@ -106,6 +106,10 @@ export function buildImageDownloadFileName(name, mime) {
|
|
|
106
106
|
// (e.g. metadata round-tripped through a file upload).
|
|
107
107
|
return safeBase.toLowerCase().endsWith(`.${extension}`) ? safeBase : `${safeBase}.${extension}`;
|
|
108
108
|
}
|
|
109
|
+
/** @deprecated Use {@link BuildImageDownloadFileName}. */
|
|
110
|
+
export function buildImageDownloadFileName(name, mime) {
|
|
111
|
+
return BuildImageDownloadFileName(name, mime);
|
|
112
|
+
}
|
|
109
113
|
/**
|
|
110
114
|
* Viewer plugin for raster image artifact versions (`image/png`, `image/jpeg`,
|
|
111
115
|
* `image/webp`, etc.). Vector SVG is handled by `SvgArtifactViewerPlugin`.
|
|
@@ -138,18 +142,58 @@ let ImageArtifactViewerComponent = class ImageArtifactViewerComponent extends Ba
|
|
|
138
142
|
fileService;
|
|
139
143
|
cdr;
|
|
140
144
|
/** Current resolved URL to bind to the `<img src>` — either a data URI (inline) or a pre-auth URL (file). */
|
|
141
|
-
|
|
145
|
+
ImageUrl = '';
|
|
146
|
+
/** @deprecated Use {@link ImageUrl}. */
|
|
147
|
+
get imageUrl() {
|
|
148
|
+
return this.ImageUrl;
|
|
149
|
+
}
|
|
150
|
+
/** @deprecated Use {@link ImageUrl}. */
|
|
151
|
+
set imageUrl(value) {
|
|
152
|
+
this.ImageUrl = value;
|
|
153
|
+
}
|
|
142
154
|
/** True from `ngOnInit` until the `<img>` `(load)` event fires (or an error happens). */
|
|
143
155
|
isLoading = true;
|
|
144
156
|
/** Set while the download is in flight, to drive the toolbar spinner. */
|
|
145
|
-
|
|
157
|
+
IsDownloading = false;
|
|
158
|
+
/** @deprecated Use {@link IsDownloading}. */
|
|
159
|
+
get isDownloading() {
|
|
160
|
+
return this.IsDownloading;
|
|
161
|
+
}
|
|
162
|
+
/** @deprecated Use {@link IsDownloading}. */
|
|
163
|
+
set isDownloading(value) {
|
|
164
|
+
this.IsDownloading = value;
|
|
165
|
+
}
|
|
146
166
|
/** User-facing error message; non-empty hides the image and shows the error block. */
|
|
147
167
|
errorMessage = '';
|
|
148
168
|
/** Click-to-toggle display mode. `'fit'` = contained in the panel, `'actual'` = 1:1 with scroll. */
|
|
149
|
-
|
|
169
|
+
DisplayMode = 'fit';
|
|
170
|
+
/** @deprecated Use {@link DisplayMode}. */
|
|
171
|
+
get displayMode() {
|
|
172
|
+
return this.DisplayMode;
|
|
173
|
+
}
|
|
174
|
+
/** @deprecated Use {@link DisplayMode}. */
|
|
175
|
+
set displayMode(value) {
|
|
176
|
+
this.DisplayMode = value;
|
|
177
|
+
}
|
|
150
178
|
/** Native image dimensions, populated by the `(load)` event. Null until then. */
|
|
151
|
-
|
|
152
|
-
|
|
179
|
+
NaturalWidth = null;
|
|
180
|
+
/** @deprecated Use {@link NaturalWidth}. */
|
|
181
|
+
get naturalWidth() {
|
|
182
|
+
return this.NaturalWidth;
|
|
183
|
+
}
|
|
184
|
+
/** @deprecated Use {@link NaturalWidth}. */
|
|
185
|
+
set naturalWidth(value) {
|
|
186
|
+
this.NaturalWidth = value;
|
|
187
|
+
}
|
|
188
|
+
NaturalHeight = null;
|
|
189
|
+
/** @deprecated Use {@link NaturalHeight}. */
|
|
190
|
+
get naturalHeight() {
|
|
191
|
+
return this.NaturalHeight;
|
|
192
|
+
}
|
|
193
|
+
/** @deprecated Use {@link NaturalHeight}. */
|
|
194
|
+
set naturalHeight(value) {
|
|
195
|
+
this.NaturalHeight = value;
|
|
196
|
+
}
|
|
153
197
|
/**
|
|
154
198
|
* Object URL we lazily build from inline data so Download / Print have a real
|
|
155
199
|
* URL to anchor on. Tracked here so `ngOnDestroy` can release it via
|
|
@@ -181,13 +225,21 @@ let ImageArtifactViewerComponent = class ImageArtifactViewerComponent extends Ba
|
|
|
181
225
|
return true;
|
|
182
226
|
}
|
|
183
227
|
/** Filename surfaced by the toolbar in the center of the bar. */
|
|
184
|
-
get
|
|
228
|
+
get ToolbarFileName() {
|
|
185
229
|
return this.artifactVersion?.FileName || this.artifactVersion?.Name || 'image';
|
|
186
230
|
}
|
|
231
|
+
/** @deprecated Use {@link ToolbarFileName}. */
|
|
232
|
+
get toolbarFileName() {
|
|
233
|
+
return this.ToolbarFileName;
|
|
234
|
+
}
|
|
187
235
|
/** Accessible alt text — prefers a descriptive name over a raw filename. */
|
|
188
|
-
get
|
|
236
|
+
get ImageAltText() {
|
|
189
237
|
return this.artifactVersion?.Name || this.artifactVersion?.FileName || 'Image artifact';
|
|
190
238
|
}
|
|
239
|
+
/** @deprecated Use {@link ImageAltText}. */
|
|
240
|
+
get imageAltText() {
|
|
241
|
+
return this.ImageAltText;
|
|
242
|
+
}
|
|
191
243
|
async ngOnInit() {
|
|
192
244
|
await this.resolveImageUrl();
|
|
193
245
|
}
|
|
@@ -223,7 +275,7 @@ let ImageArtifactViewerComponent = class ImageArtifactViewerComponent extends Ba
|
|
|
223
275
|
if (this.artifactVersion.ContentMode === 'File') {
|
|
224
276
|
// File-backed: fetch a pre-auth URL from MJStorage via the shared service.
|
|
225
277
|
// ArtifactFileService caches the URL so reopening the same artifact is instant.
|
|
226
|
-
this.
|
|
278
|
+
this.ImageUrl = await this.fileService.getDownloadUrl(this.artifactVersion.ID);
|
|
227
279
|
}
|
|
228
280
|
else {
|
|
229
281
|
// Inline ('Text' mode): Content is a data URI ready to bind directly.
|
|
@@ -232,7 +284,7 @@ let ImageArtifactViewerComponent = class ImageArtifactViewerComponent extends Ba
|
|
|
232
284
|
this.showError('This artifact has no image content.');
|
|
233
285
|
return;
|
|
234
286
|
}
|
|
235
|
-
this.
|
|
287
|
+
this.ImageUrl = content;
|
|
236
288
|
}
|
|
237
289
|
this.cdr.markForCheck();
|
|
238
290
|
}
|
|
@@ -241,63 +293,83 @@ let ImageArtifactViewerComponent = class ImageArtifactViewerComponent extends Ba
|
|
|
241
293
|
this.showError(`Could not load image: ${detail}`);
|
|
242
294
|
}
|
|
243
295
|
}
|
|
244
|
-
|
|
296
|
+
OnImageLoad(event) {
|
|
245
297
|
const img = event.target;
|
|
246
|
-
this.
|
|
247
|
-
this.
|
|
298
|
+
this.NaturalWidth = img.naturalWidth || null;
|
|
299
|
+
this.NaturalHeight = img.naturalHeight || null;
|
|
248
300
|
this.isLoading = false;
|
|
249
301
|
this.cdr.markForCheck();
|
|
250
302
|
}
|
|
251
|
-
|
|
303
|
+
/** @deprecated Use {@link OnImageLoad}. */
|
|
304
|
+
onImageLoad(event) {
|
|
305
|
+
return this.OnImageLoad(event);
|
|
306
|
+
}
|
|
307
|
+
OnImageError() {
|
|
252
308
|
this.showError('The image could not be displayed. It may be corrupt or in an unsupported format.');
|
|
253
309
|
}
|
|
310
|
+
/** @deprecated Use {@link OnImageError}. */
|
|
311
|
+
onImageError() {
|
|
312
|
+
return this.OnImageError();
|
|
313
|
+
}
|
|
254
314
|
showError(message) {
|
|
255
315
|
this.isLoading = false;
|
|
256
316
|
this.errorMessage = message;
|
|
257
317
|
this.cdr.markForCheck();
|
|
258
318
|
}
|
|
259
319
|
// ─── User actions ───────────────────────────────────────────────────────────
|
|
260
|
-
|
|
320
|
+
ToggleDisplayMode() {
|
|
261
321
|
// Don't toggle while loading or errored — the user has nothing to react to yet.
|
|
262
322
|
if (this.isLoading || this.errorMessage) {
|
|
263
323
|
return;
|
|
264
324
|
}
|
|
265
|
-
this.
|
|
325
|
+
this.DisplayMode = this.DisplayMode === 'fit' ? 'actual' : 'fit';
|
|
266
326
|
this.cdr.markForCheck();
|
|
267
327
|
}
|
|
328
|
+
/** @deprecated Use {@link ToggleDisplayMode}. */
|
|
329
|
+
toggleDisplayMode() {
|
|
330
|
+
return this.ToggleDisplayMode();
|
|
331
|
+
}
|
|
268
332
|
/**
|
|
269
333
|
* Download the image to disk. Uses the inherited `triggerBrowserDownload`
|
|
270
334
|
* helper so the fetch → blob → object-URL → anchor pattern is shared across
|
|
271
335
|
* every file-backed viewer (PDF, XLSX, DOCX, and now Image).
|
|
272
336
|
*/
|
|
273
|
-
async
|
|
274
|
-
if (this.
|
|
337
|
+
async OnDownload() {
|
|
338
|
+
if (this.IsDownloading || !this.ImageUrl) {
|
|
275
339
|
return;
|
|
276
340
|
}
|
|
277
|
-
this.
|
|
341
|
+
this.IsDownloading = true;
|
|
278
342
|
this.cdr.markForCheck();
|
|
279
343
|
try {
|
|
280
344
|
const sourceUrl = this.ensureDownloadableUrl();
|
|
281
|
-
const fileName =
|
|
345
|
+
const fileName = BuildImageDownloadFileName(this.artifactVersion?.FileName ?? this.artifactVersion?.Name ?? null, this.artifactVersion?.MimeType ?? null);
|
|
282
346
|
await this.triggerBrowserDownload(sourceUrl, fileName);
|
|
283
347
|
}
|
|
284
348
|
finally {
|
|
285
|
-
this.
|
|
349
|
+
this.IsDownloading = false;
|
|
286
350
|
this.cdr.markForCheck();
|
|
287
351
|
}
|
|
288
352
|
}
|
|
353
|
+
/** @deprecated Use {@link OnDownload}. */
|
|
354
|
+
async onDownload() {
|
|
355
|
+
return this.OnDownload();
|
|
356
|
+
}
|
|
289
357
|
/**
|
|
290
358
|
* Open the image in a new browser tab so the user can use the browser's
|
|
291
359
|
* native print dialog. We deliberately don't manage a print pipeline
|
|
292
360
|
* ourselves — same approach as `PdfArtifactViewerComponent.onPrint()`.
|
|
293
361
|
*/
|
|
294
|
-
|
|
295
|
-
if (!this.
|
|
362
|
+
OnPrint() {
|
|
363
|
+
if (!this.ImageUrl) {
|
|
296
364
|
return;
|
|
297
365
|
}
|
|
298
366
|
const sourceUrl = this.ensureDownloadableUrl();
|
|
299
367
|
window.open(sourceUrl, '_blank');
|
|
300
368
|
}
|
|
369
|
+
/** @deprecated Use {@link OnPrint}. */
|
|
370
|
+
onPrint() {
|
|
371
|
+
return this.OnPrint();
|
|
372
|
+
}
|
|
301
373
|
/**
|
|
302
374
|
* Returns a URL suitable for `<a download>` / `window.open` use.
|
|
303
375
|
* - File mode: `imageUrl` is already a pre-auth blob URL — use it directly.
|
|
@@ -307,13 +379,13 @@ let ImageArtifactViewerComponent = class ImageArtifactViewerComponent extends Ba
|
|
|
307
379
|
*/
|
|
308
380
|
ensureDownloadableUrl() {
|
|
309
381
|
if (this.artifactVersion?.ContentMode === 'File') {
|
|
310
|
-
return this.
|
|
382
|
+
return this.ImageUrl;
|
|
311
383
|
}
|
|
312
384
|
if (this.inlineObjectUrl) {
|
|
313
385
|
return this.inlineObjectUrl;
|
|
314
386
|
}
|
|
315
387
|
const mime = this.artifactVersion?.MimeType || 'application/octet-stream';
|
|
316
|
-
this.inlineObjectUrl = this.fileService.dataUrlToObjectUrl(this.
|
|
388
|
+
this.inlineObjectUrl = this.fileService.dataUrlToObjectUrl(this.ImageUrl, mime);
|
|
317
389
|
return this.inlineObjectUrl;
|
|
318
390
|
}
|
|
319
391
|
// ─── Snapshot ───────────────────────────────────────────────────────────────
|
|
@@ -328,16 +400,16 @@ let ImageArtifactViewerComponent = class ImageArtifactViewerComponent extends Ba
|
|
|
328
400
|
}
|
|
329
401
|
const snap = new DataSnapshot();
|
|
330
402
|
snap.title = this.getDisplayTitle() ?? undefined;
|
|
331
|
-
const dimensionPart = this.
|
|
403
|
+
const dimensionPart = this.NaturalWidth && this.NaturalHeight ? ` ${this.NaturalWidth} × ${this.NaturalHeight}` : '';
|
|
332
404
|
const mimeLabel = this.artifactVersion.MimeType ?? 'image';
|
|
333
405
|
snap.interpretation = `Image (${mimeLabel}${dimensionPart}).`;
|
|
334
406
|
snap.custom = {
|
|
335
407
|
mimeType: this.artifactVersion.MimeType ?? undefined,
|
|
336
408
|
fileName: this.artifactVersion.FileName ?? undefined,
|
|
337
409
|
contentMode: this.artifactVersion.ContentMode,
|
|
338
|
-
width: this.
|
|
339
|
-
height: this.
|
|
340
|
-
displayMode: this.
|
|
410
|
+
width: this.NaturalWidth ?? undefined,
|
|
411
|
+
height: this.NaturalHeight ?? undefined,
|
|
412
|
+
displayMode: this.DisplayMode,
|
|
341
413
|
};
|
|
342
414
|
return snap;
|
|
343
415
|
}
|
|
@@ -425,5 +497,5 @@ export { ImageArtifactViewerComponent };
|
|
|
425
497
|
</div>
|
|
426
498
|
`, styles: ["\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 "] }]
|
|
427
499
|
}], () => [{ type: i1.ArtifactFileService }, { type: i0.ChangeDetectorRef }], null); })();
|
|
428
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ImageArtifactViewerComponent, { className: "ImageArtifactViewerComponent", filePath: "src/lib/components/plugins/image-artifact-viewer.component.ts", lineNumber:
|
|
500
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ImageArtifactViewerComponent, { className: "ImageArtifactViewerComponent", filePath: "src/lib/components/plugins/image-artifact-viewer.component.ts", lineNumber: 245 }); })();
|
|
429
501
|
//# sourceMappingURL=image-artifact-viewer.component.js.map
|
|
@@ -1 +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, Type } 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';\nimport { IArtifactPreviewComponent } from '../../interfaces/artifact-viewer-plugin.interface';\nimport { ImageArtifactPreviewComponent } from '../previews/image-artifact-preview.component';\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 /**\n * Inline-preview component for image artifacts ({@link IArtifactViewerPluginPreviewStatics}).\n * STATIC so the resolver can read it off the registered constructor WITHOUT instantiating this\n * Angular component. Surfaces a contained thumbnail inside conversation message cards; the full\n * viewer (this class) remains the required `componentType`.\n */\n public static readonly PreviewComponentType: Type<IArtifactPreviewComponent> = ImageArtifactPreviewComponent;\n\n /** Matches raster image artifacts by type name or `image/*` MIME. STATIC — drives preview resolution. */\n public static CanHandlePreview(artifactTypeName: string, contentType?: string): boolean {\n const mime = (contentType ?? '').toLowerCase();\n if (mime.startsWith('image/')) {\n return true;\n }\n return (artifactTypeName ?? '').trim().toLowerCase() === 'image';\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"]}
|
|
1
|
+
{"version":3,"file":"image-artifact-viewer.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/image-artifact-viewer.component.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAqB,SAAS,EAA2B,MAAM,eAAe,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;AAGtF,OAAO,EAAE,6BAA6B,EAAE,MAAM,8CAA8C,CAAC;;;;;;IAuGzE,8BAA4D;IACxD,uBAA2C;IAC3C,4BAAM;IAAA,YAAkB;IAC5B,AAD4B,iBAAO,EAC7B;;;IADI,eAAkB;IAAlB,yCAAkB;;;IAIxB,8BAAiC;IAC7B,uBAAsC;IACtC,4BAAM;IAAA,mCAAc;IACxB,AADwB,iBAAO,EACzB;;;;IAMN,8BAcE;IADE,AADA,AADA,gNAAQ,0BAAmB,KAAC,+LACnB,qBAAc,KAAC,+LACf,0BAAmB,KAAC;IAbjC,iBAcE;;;IAXE,AADA,4EAA4D,+CACf;IAG7C,AADA,AADA,uDAAgB,4BACI,mGAKnB;;;IApBT,4GAAiB;IAMjB,4GAAgB;;;IANhB,2CAKC;IACD,cAmBC;IAnBD,0CAmBC;;;IAKL,8BAAsC;IAAA,YAAwC;IAAA,iBAAM;;;IAA9C,cAAwC;IAAxC,gFAAwC;;AAxI9F;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAqC,MAAM,CAAC,MAAM,CAAC;IACpF,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,KAAK;IAClB,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,KAAK;IACnB,YAAY,EAAE,MAAM;IACpB,YAAY,EAAE,MAAM;IACpB,YAAY,EAAE,MAAM;CACvB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAA+B,EAAE,IAA+B;IACvG,4EAA4E;IAC5E,yEAAyE;IACzE,MAAM,OAAO,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACnE,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC3D,MAAM,cAAc,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAClD,MAAM,SAAS,GAAG,wBAAwB,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC;IACpE,2EAA2E;IAC3E,uDAAuD;IACvD,OAAO,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,SAAS,EAAE,CAAC;AACpG,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,0BAA0B,CAAC,IAA+B,EAAE,IAA+B;IACvG,OAAO,0BAA0B,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA2JI,IAAM,4BAA4B,GAAlC,MAAM,4BAA6B,SAAQ,iCAAiC;IAyEnE;IACA;IAzEZ,6GAA6G;IACtG,QAAQ,GAAG,EAAE,CAAC;IAErB,wCAAwC;IACxC,IAAW,QAAQ;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IACD,wCAAwC;IACxC,IAAW,QAAQ,CAAC,KAAK;QACrB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,yFAAyF;IAClF,SAAS,GAAG,IAAI,CAAC;IAExB,yEAAyE;IAClE,aAAa,GAAG,KAAK,CAAC;IAE7B,6CAA6C;IAC7C,IAAW,aAAa;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC9B,CAAC;IACD,6CAA6C;IAC7C,IAAW,aAAa,CAAC,KAAK;QAC1B,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;IAC/B,CAAC;IAED,sFAAsF;IAC/E,YAAY,GAAG,EAAE,CAAC;IAEzB,oGAAoG;IAC7F,WAAW,GAAqB,KAAK,CAAC;IAE7C,2CAA2C;IAC3C,IAAW,WAAW;QAClB,OAAO,IAAI,CAAC,WAAW,CAAC;IAC5B,CAAC;IACD,2CAA2C;IAC3C,IAAW,WAAW,CAAC,KAAuB;QAC1C,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;IAC7B,CAAC;IAED,iFAAiF;IAC1E,YAAY,GAAkB,IAAI,CAAC;IAE1C,4CAA4C;IAC5C,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC7B,CAAC;IACD,4CAA4C;IAC5C,IAAW,YAAY,CAAC,KAAoB;QACxC,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAC9B,CAAC;IACM,aAAa,GAAkB,IAAI,CAAC;IAE3C,6CAA6C;IAC7C,IAAW,aAAa;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC9B,CAAC;IACD,6CAA6C;IAC7C,IAAW,aAAa,CAAC,KAAoB;QACzC,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACK,eAAe,GAAkB,IAAI,CAAC;IAE9C,YACY,WAAgC,EAChC,GAAsB;QAE9B,KAAK,EAAE,CAAC;QAHA,gBAAW,GAAX,WAAW,CAAqB;QAChC,QAAG,GAAH,GAAG,CAAmB;IAGlC,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAU,oBAAoB,GAAoC,6BAA6B,CAAC;IAE7G,yGAAyG;IAClG,MAAM,CAAC,gBAAgB,CAAC,gBAAwB,EAAE,WAAoB;QACzE,MAAM,IAAI,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/C,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC;IACrE,CAAC;IAED,8FAA8F;IAC9F,IAAoB,iBAAiB;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,iEAAiE;IACjE,IAAW,eAAe;QACtB,OAAO,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,OAAO,CAAC;IACnF,CAAC;IAED,+CAA+C;IAC/C,IAAW,eAAe;QACtB,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAED,4EAA4E;IAC5E,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,gBAAgB,CAAC;IAC5F,CAAC;IAED,4CAA4C;IAC5C,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,QAAQ;QACV,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;IACjC,CAAC;IAED,WAAW;QACP,oEAAoE;QACpE,+DAA+D;QAC/D,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC1C,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAChC,CAAC;IACL,CAAC;IAED,+EAA+E;IAE/E;;;;OAIG;IACK,KAAK,CAAC,eAAe;QACzB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,EAAE,CAAC;YAC5B,IAAI,CAAC,SAAS,CAAC,+BAA+B,CAAC,CAAC;YAChD,OAAO;QACX,CAAC;QAED,8EAA8E;QAC9E,4EAA4E;QAC5E,2EAA2E;QAC3E,6BAA6B;QAC7B,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACjE,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,SAAS,CAAC,+CAA+C,IAAI,CAAC,eAAe,CAAC,QAAQ,IAAI,CAAC,CAAC;YACjG,OAAO;QACX,CAAC;QAED,IAAI,CAAC;YACD,IAAI,IAAI,CAAC,eAAe,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;gBAC9C,2EAA2E;gBAC3E,gFAAgF;gBAChF,IAAI,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;YACnF,CAAC;iBAAM,CAAC;gBACJ,sEAAsE;gBACtE,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;gBAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;oBACX,IAAI,CAAC,SAAS,CAAC,qCAAqC,CAAC,CAAC;oBACtD,OAAO;gBACX,CAAC;gBACD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;YAC5B,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,IAAI,CAAC,SAAS,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;IACL,CAAC;IAEM,WAAW,CAAC,KAAY;QAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,MAA0B,CAAC;QAC7C,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC;QAC7C,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC;QAC/C,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAED,2CAA2C;IACpC,WAAW,CAAC,KAAY;QAC3B,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAEM,YAAY;QACf,IAAI,CAAC,SAAS,CAAC,kFAAkF,CAAC,CAAC;IACvG,CAAC;IAED,4CAA4C;IACrC,YAAY;QACf,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC;IAC/B,CAAC;IAEO,SAAS,CAAC,OAAe;QAC7B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAED,+EAA+E;IAExE,iBAAiB;QACpB,gFAAgF;QAChF,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtC,OAAO;QACX,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;QACjE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAED,iDAAiD;IAC1C,iBAAiB;QACpB,OAAO,IAAI,CAAC,iBAAiB,EAAE,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,UAAU;QACnB,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvC,OAAO;QACX,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QACxB,IAAI,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC/C,MAAM,QAAQ,GAAG,0BAA0B,CACvC,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,IAAI,EACpE,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,IAAI,CACzC,CAAC;YACF,MAAM,IAAI,CAAC,sBAAsB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC3D,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;YAC3B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC5B,CAAC;IACL,CAAC;IAED,0CAA0C;IACnC,KAAK,CAAC,UAAU;QACnB,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACI,OAAO;QACV,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,OAAO;QACX,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,uCAAuC;IAChC,OAAO;QACV,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IAED;;;;;;OAMG;IACK,qBAAqB;QACzB,IAAI,IAAI,CAAC,eAAe,EAAE,WAAW,KAAK,MAAM,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC,QAAQ,CAAC;QACzB,CAAC;QACD,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC,eAAe,CAAC;QAChC,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,0BAA0B,CAAC;QAC1E,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAChF,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAED,+EAA+E;IAE/E;;;;OAIG;IACa,uBAAuB;QACnC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,IAAI,SAAS,CAAC;QAEjD,MAAM,aAAa,GACf,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,YAAY,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnG,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,IAAI,OAAO,CAAC;QAC3D,IAAI,CAAC,cAAc,GAAG,UAAU,SAAS,GAAG,aAAa,IAAI,CAAC;QAE9D,IAAI,CAAC,MAAM,GAAG;YACV,QAAQ,EAAE,IAAI,CAAC,eAAe,CAAC,QAAQ,IAAI,SAAS;YACpD,QAAQ,EAAE,IAAI,CAAC,eAAe,CAAC,QAAQ,IAAI,SAAS;YACpD,WAAW,EAAE,IAAI,CAAC,eAAe,CAAC,WAAW;YAC7C,KAAK,EAAE,IAAI,CAAC,YAAY,IAAI,SAAS;YACrC,MAAM,EAAE,IAAI,CAAC,aAAa,IAAI,SAAS;YACvC,WAAW,EAAE,IAAI,CAAC,WAAW;SAChC,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;sHA5TQ,4BAA4B;6DAA5B,4BAA4B;YArJ7B,AADJ,8BAA+C,kCAO1C;YADG,AADA,iIAAY,gBAAY,IAAC,8GAChB,aAAS,IAAC;YAEvB,iBAA2B;YAE3B,8BAGC;YAMK,AALF,8FAAoB,8DAKX;YA4Bb,iBAAM;YAEN,8FAAoE;YAGxE,iBAAM;;YApDoB,sCAAoB;YAEtC,cAA4B;YAE5B,AADA,AADA,8CAA4B,oCACG,mBACb;YAQlB,cAA6D;YAA7D,0EAA6D;YAE7D,cAgCC;YAhCD,0CAgCC;YAGL,eAEC;YAFD,uGAEC;;;AAmGA,4BAA4B;IADxC,aAAa,CAAC,iCAAiC,EAAE,2BAA2B,CAAC;GACjE,4BAA4B,CA6TxC;;iFA7TY,4BAA4B;cA1JxC,SAAS;6BACM,KAAK,YACP,0BAA0B,YAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAsDT;;kFAiGQ,4BAA4B","sourcesContent":["import { ChangeDetectorRef, Component, OnDestroy, OnInit, Type } 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';\nimport { IArtifactPreviewComponent } from '../../interfaces/artifact-viewer-plugin.interface';\nimport { ImageArtifactPreviewComponent } from '../previews/image-artifact-preview.component';\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/** @deprecated Use {@link BuildImageDownloadFileName}. */\nexport function buildImageDownloadFileName(name: string | null | undefined, mime: string | null | undefined): string {\n return BuildImageDownloadFileName(name, mime);\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 /** @deprecated Use {@link ImageUrl}. */\n public get imageUrl() {\n return this.ImageUrl;\n }\n /** @deprecated Use {@link ImageUrl}. */\n public set imageUrl(value) {\n this.ImageUrl = value;\n }\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 /** @deprecated Use {@link IsDownloading}. */\n public get isDownloading() {\n return this.IsDownloading;\n }\n /** @deprecated Use {@link IsDownloading}. */\n public set isDownloading(value) {\n this.IsDownloading = value;\n }\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 /** @deprecated Use {@link DisplayMode}. */\n public get displayMode(): 'fit' | 'actual' {\n return this.DisplayMode;\n }\n /** @deprecated Use {@link DisplayMode}. */\n public set displayMode(value: 'fit' | 'actual') {\n this.DisplayMode = value;\n }\n\n /** Native image dimensions, populated by the `(load)` event. Null until then. */\n public NaturalWidth: number | null = null;\n\n /** @deprecated Use {@link NaturalWidth}. */\n public get naturalWidth(): number | null {\n return this.NaturalWidth;\n }\n /** @deprecated Use {@link NaturalWidth}. */\n public set naturalWidth(value: number | null) {\n this.NaturalWidth = value;\n }\n public NaturalHeight: number | null = null;\n\n /** @deprecated Use {@link NaturalHeight}. */\n public get naturalHeight(): number | null {\n return this.NaturalHeight;\n }\n /** @deprecated Use {@link NaturalHeight}. */\n public set naturalHeight(value: number | null) {\n this.NaturalHeight = value;\n }\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 /**\n * Inline-preview component for image artifacts ({@link IArtifactViewerPluginPreviewStatics}).\n * STATIC so the resolver can read it off the registered constructor WITHOUT instantiating this\n * Angular component. Surfaces a contained thumbnail inside conversation message cards; the full\n * viewer (this class) remains the required `componentType`.\n */\n public static readonly PreviewComponentType: Type<IArtifactPreviewComponent> = ImageArtifactPreviewComponent;\n\n /** Matches raster image artifacts by type name or `image/*` MIME. STATIC — drives preview resolution. */\n public static CanHandlePreview(artifactTypeName: string, contentType?: string): boolean {\n const mime = (contentType ?? '').toLowerCase();\n if (mime.startsWith('image/')) {\n return true;\n }\n return (artifactTypeName ?? '').trim().toLowerCase() === 'image';\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 /** @deprecated Use {@link ToolbarFileName}. */\n public get toolbarFileName(): string {\n return this.ToolbarFileName;\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 /** @deprecated Use {@link ImageAltText}. */\n public get imageAltText(): string {\n return this.ImageAltText;\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 /** @deprecated Use {@link OnImageLoad}. */\n public onImageLoad(event: Event): void {\n return this.OnImageLoad(event);\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 /** @deprecated Use {@link OnImageError}. */\n public onImageError(): void {\n return this.OnImageError();\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 /** @deprecated Use {@link ToggleDisplayMode}. */\n public toggleDisplayMode(): void {\n return this.ToggleDisplayMode();\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 /** @deprecated Use {@link OnDownload}. */\n public async onDownload(): Promise<void> {\n return this.OnDownload();\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 /** @deprecated Use {@link OnPrint}. */\n public onPrint(): void {\n return this.OnPrint();\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"]}
|
|
@@ -11,11 +11,31 @@ import * as i0 from "@angular/core";
|
|
|
11
11
|
export declare class JsonArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit, OnDestroy {
|
|
12
12
|
private cdr;
|
|
13
13
|
private sanitizer;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
HtmlFrame?: ElementRef<HTMLIFrameElement>;
|
|
15
|
+
/** @deprecated Use {@link HtmlFrame}. */
|
|
16
|
+
get htmlFrame(): ElementRef<HTMLIFrameElement> | undefined;
|
|
17
|
+
/** @deprecated Use {@link HtmlFrame}. */
|
|
18
|
+
set htmlFrame(value: ElementRef<HTMLIFrameElement> | undefined);
|
|
19
|
+
JsonContent: string;
|
|
20
|
+
/** @deprecated Use {@link JsonContent}. */
|
|
21
|
+
get jsonContent(): string;
|
|
22
|
+
/** @deprecated Use {@link JsonContent}. */
|
|
23
|
+
set jsonContent(value: string);
|
|
24
|
+
DisplayMarkdown: string | null;
|
|
25
|
+
/** @deprecated Use {@link DisplayMarkdown}. */
|
|
26
|
+
get displayMarkdown(): string | null;
|
|
27
|
+
/** @deprecated Use {@link DisplayMarkdown}. */
|
|
28
|
+
set displayMarkdown(value: string | null);
|
|
29
|
+
DisplayHtml: string | null;
|
|
30
|
+
/** @deprecated Use {@link DisplayHtml}. */
|
|
31
|
+
get displayHtml(): string | null;
|
|
32
|
+
/** @deprecated Use {@link DisplayHtml}. */
|
|
33
|
+
set displayHtml(value: string | null);
|
|
34
|
+
HtmlBlobUrl: SafeResourceUrl | null;
|
|
35
|
+
/** @deprecated Use {@link HtmlBlobUrl}. */
|
|
36
|
+
get htmlBlobUrl(): SafeResourceUrl | null;
|
|
37
|
+
/** @deprecated Use {@link HtmlBlobUrl}. */
|
|
38
|
+
set htmlBlobUrl(value: SafeResourceUrl | null);
|
|
19
39
|
private versionAttributes;
|
|
20
40
|
private unsafeBlobUrl;
|
|
21
41
|
/**
|
|
@@ -46,10 +66,18 @@ export declare class JsonArtifactViewerComponent extends BaseArtifactViewerPlugi
|
|
|
46
66
|
* style/resize enhancements below are optional niceties, not requirements.
|
|
47
67
|
*/
|
|
48
68
|
private getIframeDocument;
|
|
69
|
+
OnIframeLoad(): void;
|
|
70
|
+
/** @deprecated Use {@link OnIframeLoad}. */
|
|
49
71
|
onIframeLoad(): void;
|
|
50
72
|
private resizeIframeToContent;
|
|
73
|
+
OpenInNewWindow(): void;
|
|
74
|
+
/** @deprecated Use {@link OpenInNewWindow}. */
|
|
51
75
|
openInNewWindow(): void;
|
|
76
|
+
PrintHtml(): void;
|
|
77
|
+
/** @deprecated Use {@link PrintHtml}. */
|
|
52
78
|
printHtml(): void;
|
|
79
|
+
OnCopy(): void;
|
|
80
|
+
/** @deprecated Use {@link OnCopy}. */
|
|
53
81
|
onCopy(): void;
|
|
54
82
|
GetCurrentStateSnapshot(): DataSnapshot | null;
|
|
55
83
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-artifact-viewer.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/json-artifact-viewer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAAa,UAAU,EAAE,MAAM,eAAe,CAAC;AAEvG,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;AACtF,OAAO,EAAE,YAAY,EAAW,MAAM,sBAAsB,CAAC;;AAI7D;;;;GAIG;AACH,qBA8Ha,2BAA4B,SAAQ,iCAAkC,YAAW,MAAM,EAAE,SAAS;
|
|
1
|
+
{"version":3,"file":"json-artifact-viewer.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/json-artifact-viewer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAAa,UAAU,EAAE,MAAM,eAAe,CAAC;AAEvG,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;AACtF,OAAO,EAAE,YAAY,EAAW,MAAM,sBAAsB,CAAC;;AAI7D;;;;GAIG;AACH,qBA8Ha,2BAA4B,SAAQ,iCAAkC,YAAW,MAAM,EAAE,SAAS;IA+D3G,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,SAAS;IA/DK,SAAS,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAElE,yCAAyC;IACzC,IAAI,SAAS,IAAI,UAAU,CAAC,iBAAiB,CAAC,GAAG,SAAS,CAEzD;IACD,yCAAyC;IACzC,IAAI,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,SAAS,EAE7D;IAEM,WAAW,SAAM;IAExB,2CAA2C;IAC3C,IAAW,WAAW,WAErB;IACD,2CAA2C;IAC3C,IAAW,WAAW,CAAC,KAAK,QAAA,EAE3B;IACM,eAAe,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE7C,+CAA+C;IAC/C,IAAW,eAAe,IAAI,MAAM,GAAG,IAAI,CAE1C;IACD,+CAA+C;IAC/C,IAAW,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAE9C;IACM,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEzC,2CAA2C;IAC3C,IAAW,WAAW,IAAI,MAAM,GAAG,IAAI,CAEtC;IACD,2CAA2C;IAC3C,IAAW,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAE1C;IACM,WAAW,EAAE,eAAe,GAAG,IAAI,CAAQ;IAElD,2CAA2C;IAC3C,IAAW,WAAW,IAAI,eAAe,GAAG,IAAI,CAE/C;IACD,2CAA2C;IAC3C,IAAW,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,EAEnD;IACD,OAAO,CAAC,iBAAiB,CAA0C;IACnE,OAAO,CAAC,aAAa,CAAuB;IAE5C;;OAEG;IACH,IAAoB,iBAAiB,IAAI,OAAO,CAE/C;gBAGS,GAAG,EAAE,iBAAiB,EACtB,SAAS,EAAE,YAAY;IAKjC,WAAW,IAAI,IAAI;IASb,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAU/B;;;OAGG;IACH,IAAoB,wBAAwB,IAAI,OAAO,CAEtD;IAED;;;;OAIG;IACH,IAAoB,0BAA0B,IAAI,OAAO,CAExD;YAEa,qBAAqB;IA6DnC,OAAO,CAAC,mBAAmB;IAyB3B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAUzB,YAAY,IAAI,IAAI;IA0DpB,4CAA4C;IAC5C,YAAY,IAAI,IAAI;IAIpB,OAAO,CAAC,qBAAqB;IAwB7B,eAAe,IAAI,IAAI;IAUvB,+CAA+C;IAC/C,eAAe,IAAI,IAAI;IAIvB,SAAS,IAAI,IAAI;IAkBjB,yCAAyC;IACzC,SAAS,IAAI,IAAI;IAIjB,MAAM,IAAI,IAAI;IAYd,sCAAsC;IACtC,MAAM,IAAI,IAAI;IAIE,uBAAuB,IAAI,YAAY,GAAG,IAAI;IAI9D;;;OAGG;IACH,OAAO,CAAC,sBAAsB;yCAtWnB,2BAA2B;2CAA3B,2BAA2B;CAyXvC"}
|