@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.
Files changed (125) hide show
  1. package/dist/lib/components/artifact-message-card.component.d.ts +60 -8
  2. package/dist/lib/components/artifact-message-card.component.d.ts.map +1 -1
  3. package/dist/lib/components/artifact-message-card.component.js +135 -39
  4. package/dist/lib/components/artifact-message-card.component.js.map +1 -1
  5. package/dist/lib/components/artifact-type-plugin-viewer.component.d.ts +58 -6
  6. package/dist/lib/components/artifact-type-plugin-viewer.component.d.ts.map +1 -1
  7. package/dist/lib/components/artifact-type-plugin-viewer.component.js +107 -29
  8. package/dist/lib/components/artifact-type-plugin-viewer.component.js.map +1 -1
  9. package/dist/lib/components/artifact-version-download.d.ts +5 -3
  10. package/dist/lib/components/artifact-version-download.d.ts.map +1 -1
  11. package/dist/lib/components/artifact-version-download.js +11 -7
  12. package/dist/lib/components/artifact-version-download.js.map +1 -1
  13. package/dist/lib/components/artifact-version-history.component.d.ts +74 -8
  14. package/dist/lib/components/artifact-version-history.component.d.ts.map +1 -1
  15. package/dist/lib/components/artifact-version-history.component.js +152 -34
  16. package/dist/lib/components/artifact-version-history.component.js.map +1 -1
  17. package/dist/lib/components/artifact-viewer-panel.component.d.ts +328 -35
  18. package/dist/lib/components/artifact-viewer-panel.component.d.ts.map +1 -1
  19. package/dist/lib/components/artifact-viewer-panel.component.js +701 -213
  20. package/dist/lib/components/artifact-viewer-panel.component.js.map +1 -1
  21. package/dist/lib/components/file-artifact-toolbar.component.d.ts +112 -10
  22. package/dist/lib/components/file-artifact-toolbar.component.d.ts.map +1 -1
  23. package/dist/lib/components/file-artifact-toolbar.component.js +197 -23
  24. package/dist/lib/components/file-artifact-toolbar.component.js.map +1 -1
  25. package/dist/lib/components/plugins/audio-artifact-viewer.component.d.ts +9 -1
  26. package/dist/lib/components/plugins/audio-artifact-viewer.component.d.ts.map +1 -1
  27. package/dist/lib/components/plugins/audio-artifact-viewer.component.js +20 -4
  28. package/dist/lib/components/plugins/audio-artifact-viewer.component.js.map +1 -1
  29. package/dist/lib/components/plugins/code-artifact-viewer.component.d.ts +23 -5
  30. package/dist/lib/components/plugins/code-artifact-viewer.component.d.ts.map +1 -1
  31. package/dist/lib/components/plugins/code-artifact-viewer.component.js +69 -31
  32. package/dist/lib/components/plugins/code-artifact-viewer.component.js.map +1 -1
  33. package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts +136 -18
  34. package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts.map +1 -1
  35. package/dist/lib/components/plugins/component-artifact-viewer.component.js +285 -97
  36. package/dist/lib/components/plugins/component-artifact-viewer.component.js.map +1 -1
  37. package/dist/lib/components/plugins/component-feedback-panel/component-feedback-panel.component.d.ts +2 -2
  38. package/dist/lib/components/plugins/component-feedback-panel/component-feedback-panel.component.js +7 -7
  39. package/dist/lib/components/plugins/component-feedback-panel/component-feedback-panel.component.js.map +1 -1
  40. package/dist/lib/components/plugins/component-permission-evaluation.d.ts +2 -0
  41. package/dist/lib/components/plugins/component-permission-evaluation.d.ts.map +1 -1
  42. package/dist/lib/components/plugins/component-permission-evaluation.js +6 -2
  43. package/dist/lib/components/plugins/component-permission-evaluation.js.map +1 -1
  44. package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts +17 -6
  45. package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts.map +1 -1
  46. package/dist/lib/components/plugins/data-artifact-viewer.component.js +32 -22
  47. package/dist/lib/components/plugins/data-artifact-viewer.component.js.map +1 -1
  48. package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.d.ts +56 -4
  49. package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.d.ts.map +1 -1
  50. package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.js +142 -36
  51. package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.js.map +1 -1
  52. package/dist/lib/components/plugins/docx-artifact-viewer.component.d.ts +14 -2
  53. package/dist/lib/components/plugins/docx-artifact-viewer.component.d.ts.map +1 -1
  54. package/dist/lib/components/plugins/docx-artifact-viewer.component.js +32 -8
  55. package/dist/lib/components/plugins/docx-artifact-viewer.component.js.map +1 -1
  56. package/dist/lib/components/plugins/html-artifact-viewer.component.d.ts +17 -3
  57. package/dist/lib/components/plugins/html-artifact-viewer.component.d.ts.map +1 -1
  58. package/dist/lib/components/plugins/html-artifact-viewer.component.js +36 -8
  59. package/dist/lib/components/plugins/html-artifact-viewer.component.js.map +1 -1
  60. package/dist/lib/components/plugins/image-artifact-viewer.component.d.ts +41 -5
  61. package/dist/lib/components/plugins/image-artifact-viewer.component.d.ts.map +1 -1
  62. package/dist/lib/components/plugins/image-artifact-viewer.component.js +102 -30
  63. package/dist/lib/components/plugins/image-artifact-viewer.component.js.map +1 -1
  64. package/dist/lib/components/plugins/json-artifact-viewer.component.d.ts +33 -5
  65. package/dist/lib/components/plugins/json-artifact-viewer.component.d.ts.map +1 -1
  66. package/dist/lib/components/plugins/json-artifact-viewer.component.js +89 -33
  67. package/dist/lib/components/plugins/json-artifact-viewer.component.js.map +1 -1
  68. package/dist/lib/components/plugins/markdown-artifact-viewer.component.d.ts +12 -2
  69. package/dist/lib/components/plugins/markdown-artifact-viewer.component.d.ts.map +1 -1
  70. package/dist/lib/components/plugins/markdown-artifact-viewer.component.js +28 -8
  71. package/dist/lib/components/plugins/markdown-artifact-viewer.component.js.map +1 -1
  72. package/dist/lib/components/plugins/ml-experiment-results-viewer.component.d.ts +4 -0
  73. package/dist/lib/components/plugins/ml-experiment-results-viewer.component.d.ts.map +1 -1
  74. package/dist/lib/components/plugins/ml-experiment-results-viewer.component.js +14 -6
  75. package/dist/lib/components/plugins/ml-experiment-results-viewer.component.js.map +1 -1
  76. package/dist/lib/components/plugins/pdf-artifact-viewer.component.d.ts +47 -5
  77. package/dist/lib/components/plugins/pdf-artifact-viewer.component.d.ts.map +1 -1
  78. package/dist/lib/components/plugins/pdf-artifact-viewer.component.js +129 -45
  79. package/dist/lib/components/plugins/pdf-artifact-viewer.component.js.map +1 -1
  80. package/dist/lib/components/plugins/save-query-dialog.component.d.ts +5 -1
  81. package/dist/lib/components/plugins/save-query-dialog.component.d.ts.map +1 -1
  82. package/dist/lib/components/plugins/save-query-dialog.component.js +12 -4
  83. package/dist/lib/components/plugins/save-query-dialog.component.js.map +1 -1
  84. package/dist/lib/components/plugins/svg-artifact-viewer.component.d.ts +17 -3
  85. package/dist/lib/components/plugins/svg-artifact-viewer.component.d.ts.map +1 -1
  86. package/dist/lib/components/plugins/svg-artifact-viewer.component.js +36 -8
  87. package/dist/lib/components/plugins/svg-artifact-viewer.component.js.map +1 -1
  88. package/dist/lib/components/plugins/video-artifact-viewer.component.d.ts +9 -1
  89. package/dist/lib/components/plugins/video-artifact-viewer.component.d.ts.map +1 -1
  90. package/dist/lib/components/plugins/video-artifact-viewer.component.js +20 -4
  91. package/dist/lib/components/plugins/video-artifact-viewer.component.js.map +1 -1
  92. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.d.ts +28 -4
  93. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.d.ts.map +1 -1
  94. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js +64 -16
  95. package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js.map +1 -1
  96. package/dist/lib/components/previews/audio-artifact-preview.component.d.ts +9 -1
  97. package/dist/lib/components/previews/audio-artifact-preview.component.d.ts.map +1 -1
  98. package/dist/lib/components/previews/audio-artifact-preview.component.js +20 -4
  99. package/dist/lib/components/previews/audio-artifact-preview.component.js.map +1 -1
  100. package/dist/lib/components/previews/image-artifact-preview.component.d.ts +16 -2
  101. package/dist/lib/components/previews/image-artifact-preview.component.d.ts.map +1 -1
  102. package/dist/lib/components/previews/image-artifact-preview.component.js +35 -7
  103. package/dist/lib/components/previews/image-artifact-preview.component.js.map +1 -1
  104. package/dist/lib/components/previews/video-artifact-preview.component.d.ts +9 -1
  105. package/dist/lib/components/previews/video-artifact-preview.component.d.ts.map +1 -1
  106. package/dist/lib/components/previews/video-artifact-preview.component.js +20 -4
  107. package/dist/lib/components/previews/video-artifact-preview.component.js.map +1 -1
  108. package/dist/lib/interfaces/artifact-viewer-plugin.interface.js.map +1 -1
  109. package/dist/lib/services/artifact-file.service.d.ts +8 -0
  110. package/dist/lib/services/artifact-file.service.d.ts.map +1 -1
  111. package/dist/lib/services/artifact-file.service.js +22 -6
  112. package/dist/lib/services/artifact-file.service.js.map +1 -1
  113. package/dist/lib/services/artifact-icon.service.d.ts +2 -0
  114. package/dist/lib/services/artifact-icon.service.d.ts.map +1 -1
  115. package/dist/lib/services/artifact-icon.service.js +5 -1
  116. package/dist/lib/services/artifact-icon.service.js.map +1 -1
  117. package/dist/lib/services/artifact-preview-resolver.service.d.ts +2 -0
  118. package/dist/lib/services/artifact-preview-resolver.service.d.ts.map +1 -1
  119. package/dist/lib/services/artifact-preview-resolver.service.js +5 -1
  120. package/dist/lib/services/artifact-preview-resolver.service.js.map +1 -1
  121. package/dist/lib/snapshot-helpers.d.ts +10 -0
  122. package/dist/lib/snapshot-helpers.d.ts.map +1 -1
  123. package/dist/lib/snapshot-helpers.js +25 -5
  124. package/dist/lib/snapshot-helpers.js.map +1 -1
  125. 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 buildImageDownloadFileName(name, mime) {
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
- imageUrl = '';
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
- isDownloading = false;
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
- displayMode = 'fit';
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
- naturalWidth = null;
152
- naturalHeight = null;
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 toolbarFileName() {
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 imageAltText() {
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.imageUrl = await this.fileService.getDownloadUrl(this.artifactVersion.ID);
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.imageUrl = content;
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
- onImageLoad(event) {
296
+ OnImageLoad(event) {
245
297
  const img = event.target;
246
- this.naturalWidth = img.naturalWidth || null;
247
- this.naturalHeight = img.naturalHeight || null;
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
- onImageError() {
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
- toggleDisplayMode() {
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.displayMode = this.displayMode === 'fit' ? 'actual' : 'fit';
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 onDownload() {
274
- if (this.isDownloading || !this.imageUrl) {
337
+ async OnDownload() {
338
+ if (this.IsDownloading || !this.ImageUrl) {
275
339
  return;
276
340
  }
277
- this.isDownloading = true;
341
+ this.IsDownloading = true;
278
342
  this.cdr.markForCheck();
279
343
  try {
280
344
  const sourceUrl = this.ensureDownloadableUrl();
281
- const fileName = buildImageDownloadFileName(this.artifactVersion?.FileName ?? this.artifactVersion?.Name ?? null, this.artifactVersion?.MimeType ?? null);
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.isDownloading = false;
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
- onPrint() {
295
- if (!this.imageUrl) {
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.imageUrl;
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.imageUrl, mime);
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.naturalWidth && this.naturalHeight ? ` ${this.naturalWidth} × ${this.naturalHeight}` : '';
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.naturalWidth ?? undefined,
339
- height: this.naturalHeight ?? undefined,
340
- displayMode: this.displayMode,
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: 240 }); })();
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":"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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/** @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
- htmlFrame?: ElementRef<HTMLIFrameElement>;
15
- jsonContent: string;
16
- displayMarkdown: string | null;
17
- displayHtml: string | null;
18
- htmlBlobUrl: SafeResourceUrl | null;
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;IAkB3G,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,SAAS;IAlBK,SAAS,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAE3D,WAAW,SAAM;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAQ;IACtC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAClC,WAAW,EAAE,eAAe,GAAG,IAAI,CAAQ;IAClD,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,OAAO,CAAC,qBAAqB;IAwB7B,eAAe,IAAI,IAAI;IAUvB,SAAS,IAAI,IAAI;IAkBjB,MAAM,IAAI,IAAI;IAYE,uBAAuB,IAAI,YAAY,GAAG,IAAI;IAI9D;;;OAGG;IACH,OAAO,CAAC,sBAAsB;yCArSnB,2BAA2B;2CAA3B,2BAA2B;CAwTvC"}
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"}