@memberjunction/ng-artifacts 6.1.0-edge.5 → 6.1.0-edge.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/lib/components/artifact-viewer-panel.component.d.ts +30 -1
  2. package/dist/lib/components/artifact-viewer-panel.component.d.ts.map +1 -1
  3. package/dist/lib/components/artifact-viewer-panel.component.js +283 -89
  4. package/dist/lib/components/artifact-viewer-panel.component.js.map +1 -1
  5. package/dist/lib/components/file-artifact-toolbar.component.js +1 -1
  6. package/dist/lib/components/plugins/component-artifact-viewer.component.js +2 -2
  7. package/dist/lib/components/plugins/component-artifact-viewer.component.js.map +1 -1
  8. package/dist/lib/components/plugins/component-feedback-panel/component-feedback-panel.component.js +1 -1
  9. package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts.map +1 -1
  10. package/dist/lib/components/plugins/data-artifact-viewer.component.js +6 -8
  11. package/dist/lib/components/plugins/data-artifact-viewer.component.js.map +1 -1
  12. package/dist/lib/components/plugins/data-requirements-viewer/data-requirements-viewer.component.js +1 -1
  13. package/dist/lib/components/plugins/docx-artifact-viewer.component.js +1 -1
  14. package/dist/lib/components/plugins/image-artifact-viewer.component.js +1 -1
  15. package/dist/lib/components/plugins/json-artifact-viewer.component.d.ts +8 -0
  16. package/dist/lib/components/plugins/json-artifact-viewer.component.d.ts.map +1 -1
  17. package/dist/lib/components/plugins/json-artifact-viewer.component.js +45 -14
  18. package/dist/lib/components/plugins/json-artifact-viewer.component.js.map +1 -1
  19. package/dist/lib/components/plugins/ml-experiment-results-viewer.component.js +1 -1
  20. package/dist/lib/components/plugins/pdf-artifact-viewer.component.js +1 -1
  21. package/dist/lib/components/plugins/save-query-dialog.component.js +7 -7
  22. package/dist/lib/components/plugins/save-query-dialog.component.js.map +1 -1
  23. package/dist/lib/components/previews/audio-artifact-preview.component.d.ts.map +1 -1
  24. package/dist/lib/components/previews/audio-artifact-preview.component.js +15 -9
  25. package/dist/lib/components/previews/audio-artifact-preview.component.js.map +1 -1
  26. package/dist/lib/components/previews/base-artifact-preview.component.d.ts +13 -0
  27. package/dist/lib/components/previews/base-artifact-preview.component.d.ts.map +1 -1
  28. package/dist/lib/components/previews/base-artifact-preview.component.js +20 -0
  29. package/dist/lib/components/previews/base-artifact-preview.component.js.map +1 -1
  30. package/dist/lib/components/previews/image-artifact-preview.component.d.ts +13 -0
  31. package/dist/lib/components/previews/image-artifact-preview.component.d.ts.map +1 -1
  32. package/dist/lib/components/previews/image-artifact-preview.component.js +55 -19
  33. package/dist/lib/components/previews/image-artifact-preview.component.js.map +1 -1
  34. package/dist/lib/components/previews/video-artifact-preview.component.d.ts.map +1 -1
  35. package/dist/lib/components/previews/video-artifact-preview.component.js +15 -9
  36. package/dist/lib/components/previews/video-artifact-preview.component.js.map +1 -1
  37. package/package.json +29 -29
@@ -19,14 +19,27 @@ export declare abstract class BaseArtifactPreviewComponent extends BaseAngularCo
19
19
  artifactVersion: MJArtifactVersionEntity;
20
20
  /** Shared service used by both previews and full viewers to fetch file-mode download URLs. */
21
21
  protected readonly fileService: ArtifactFileService;
22
+ /**
23
+ * True until {@link resolveContentUrl} has settled, one way or the other.
24
+ *
25
+ * Owned here, not per subclass, so that every media preview has a loading state by
26
+ * construction. Each preview used to carry its own copy of this flag — or, before that, no
27
+ * loading branch at all, which rendered nothing while the URL resolved and made a generated
28
+ * image read as "never arrived". `resolveContentUrl` is async even for inline content, and
29
+ * for `ContentMode === 'File'` it is a network round trip before the media fetch even starts.
30
+ */
31
+ isLoading: boolean;
22
32
  /**
23
33
  * Resolve a URL to bind to a media element (`<img>`, `<video>`, `<audio>`):
24
34
  * - `ContentMode === 'File'` → fetch a pre-authenticated download URL from MJStorage.
25
35
  * - otherwise (`'Text'`) → `Content` is already a `data:<mime>;base64,…` URI; bind directly.
26
36
  *
27
37
  * Returns `null` when there is no usable content (caller should show its own empty/error state).
38
+ * Clears {@link isLoading} on every exit, including a throw, so a subclass cannot leave the
39
+ * indicator spinning beside an error.
28
40
  */
29
41
  protected resolveContentUrl(): Promise<string | null>;
42
+ private resolveContentUrlCore;
30
43
  static ɵfac: i0.ɵɵFactoryDeclaration<BaseArtifactPreviewComponent, never>;
31
44
  static ɵcmp: i0.ɵɵComponentDeclaration<BaseArtifactPreviewComponent, "ng-component", never, { "artifactVersion": { "alias": "artifactVersion"; "required": false; }; }, {}, never, never, false, never>;
32
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"base-artifact-preview.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/previews/base-artifact-preview.component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAC9F,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;;AAE3E;;;;;;;;;;GAUG;AACH,8BAIsB,4BAA6B,SAAQ,oBAAqB,YAAW,yBAAyB;IAChH,yFAAyF;IAChF,eAAe,EAAG,uBAAuB,CAAC;IAEnD,8FAA8F;IAC9F,SAAS,CAAC,QAAQ,CAAC,WAAW,sBAA+B;IAE7D;;;;;;OAMG;cACa,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;yCAdzC,4BAA4B;2CAA5B,4BAA4B;CA2BjD"}
1
+ {"version":3,"file":"base-artifact-preview.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/previews/base-artifact-preview.component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAC9F,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;;AAE3E;;;;;;;;;;GAUG;AACH,8BAIsB,4BAA6B,SAAQ,oBAAqB,YAAW,yBAAyB;IAChH,yFAAyF;IAChF,eAAe,EAAG,uBAAuB,CAAC;IAEnD,8FAA8F;IAC9F,SAAS,CAAC,QAAQ,CAAC,WAAW,sBAA+B;IAE7D;;;;;;;;OAQG;IACI,SAAS,UAAQ;IAExB;;;;;;;;OAQG;cACa,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAQ7C,qBAAqB;yCAnCjB,4BAA4B;2CAA5B,4BAA4B;CAgDjD"}
@@ -18,14 +18,34 @@ export class BaseArtifactPreviewComponent extends BaseAngularComponent {
18
18
  artifactVersion;
19
19
  /** Shared service used by both previews and full viewers to fetch file-mode download URLs. */
20
20
  fileService = inject(ArtifactFileService);
21
+ /**
22
+ * True until {@link resolveContentUrl} has settled, one way or the other.
23
+ *
24
+ * Owned here, not per subclass, so that every media preview has a loading state by
25
+ * construction. Each preview used to carry its own copy of this flag — or, before that, no
26
+ * loading branch at all, which rendered nothing while the URL resolved and made a generated
27
+ * image read as "never arrived". `resolveContentUrl` is async even for inline content, and
28
+ * for `ContentMode === 'File'` it is a network round trip before the media fetch even starts.
29
+ */
30
+ isLoading = true;
21
31
  /**
22
32
  * Resolve a URL to bind to a media element (`<img>`, `<video>`, `<audio>`):
23
33
  * - `ContentMode === 'File'` → fetch a pre-authenticated download URL from MJStorage.
24
34
  * - otherwise (`'Text'`) → `Content` is already a `data:<mime>;base64,…` URI; bind directly.
25
35
  *
26
36
  * Returns `null` when there is no usable content (caller should show its own empty/error state).
37
+ * Clears {@link isLoading} on every exit, including a throw, so a subclass cannot leave the
38
+ * indicator spinning beside an error.
27
39
  */
28
40
  async resolveContentUrl() {
41
+ try {
42
+ return await this.resolveContentUrlCore();
43
+ }
44
+ finally {
45
+ this.isLoading = false;
46
+ }
47
+ }
48
+ async resolveContentUrlCore() {
29
49
  const version = this.artifactVersion;
30
50
  if (!version?.ID) {
31
51
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"base-artifact-preview.component.js","sourceRoot":"","sources":["../../../../src/lib/components/previews/base-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;;AAE3E;;;;;;;;;;GAUG;AAKH,MAAM,OAAgB,4BAA6B,SAAQ,oBAAoB;IAC3E,yFAAyF;IAChF,eAAe,CAA2B;IAEnD,8FAA8F;IAC3E,WAAW,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE7D;;;;;;OAMG;IACO,KAAK,CAAC,iBAAiB;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC;YACf,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,OAAO,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,OAAO,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;6RA1BiB,4BAA4B,yBAA5B,4BAA4B;6DAA5B,4BAA4B;;iFAA5B,4BAA4B;cAJjD,SAAS;eAAC;gBACP,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,EAAE;aACf;;kBAGI,KAAK;;kFAFY,4BAA4B","sourcesContent":["import { Component, inject, Input } from '@angular/core';\nimport { MJArtifactVersionEntity } from '@memberjunction/core-entities';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { IArtifactPreviewComponent } from '../../interfaces/artifact-viewer-plugin.interface';\nimport { ArtifactFileService } from '../../services/artifact-file.service';\n\n/**\n * Thin abstract base for inline artifact PREVIEW components.\n *\n * Deliberately does NOT extend the heavy {@link BaseArtifactViewerPluginComponent} — previews\n * are lightweight, read-only, and have no toolbar / feedback / tabs / snapshot contract. They\n * render a compact representation of the artifact inside a conversation message card.\n *\n * Content resolution (inline data-URL vs. file-storage download URL) is shared with the full\n * viewers via the injected {@link ArtifactFileService}, so previews and viewers load bytes the\n * same way. Subclasses call {@link resolveContentUrl} in their `ngOnInit`.\n */\n@Component({\n standalone: false,\n template: '',\n})\nexport abstract class BaseArtifactPreviewComponent extends BaseAngularComponent implements IArtifactPreviewComponent {\n /** The artifact version to preview. Set by the resolver before the component renders. */\n @Input() artifactVersion!: MJArtifactVersionEntity;\n\n /** Shared service used by both previews and full viewers to fetch file-mode download URLs. */\n protected readonly fileService = inject(ArtifactFileService);\n\n /**\n * Resolve a URL to bind to a media element (`<img>`, `<video>`, `<audio>`):\n * - `ContentMode === 'File'` → fetch a pre-authenticated download URL from MJStorage.\n * - otherwise (`'Text'`) → `Content` is already a `data:<mime>;base64,…` URI; bind directly.\n *\n * Returns `null` when there is no usable content (caller should show its own empty/error state).\n */\n protected async resolveContentUrl(): Promise<string | null> {\n const version = this.artifactVersion;\n if (!version?.ID) {\n return null;\n }\n\n if (version.ContentMode === 'File') {\n return this.fileService.getDownloadUrl(version.ID);\n }\n\n const content = version.Content;\n return content && content.length > 0 ? content : null;\n }\n}\n"]}
1
+ {"version":3,"file":"base-artifact-preview.component.js","sourceRoot":"","sources":["../../../../src/lib/components/previews/base-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;;AAE3E;;;;;;;;;;GAUG;AAKH,MAAM,OAAgB,4BAA6B,SAAQ,oBAAoB;IAC3E,yFAAyF;IAChF,eAAe,CAA2B;IAEnD,8FAA8F;IAC3E,WAAW,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE7D;;;;;;;;OAQG;IACI,SAAS,GAAG,IAAI,CAAC;IAExB;;;;;;;;OAQG;IACO,KAAK,CAAC,iBAAiB;QAC7B,IAAI,CAAC;YACD,OAAO,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC9C,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QAC3B,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,qBAAqB;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC;YACf,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,OAAO,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,OAAO,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;6RA/CiB,4BAA4B,yBAA5B,4BAA4B;6DAA5B,4BAA4B;;iFAA5B,4BAA4B;cAJjD,SAAS;eAAC;gBACP,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,EAAE;aACf;;kBAGI,KAAK;;kFAFY,4BAA4B","sourcesContent":["import { Component, inject, Input } from '@angular/core';\nimport { MJArtifactVersionEntity } from '@memberjunction/core-entities';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { IArtifactPreviewComponent } from '../../interfaces/artifact-viewer-plugin.interface';\nimport { ArtifactFileService } from '../../services/artifact-file.service';\n\n/**\n * Thin abstract base for inline artifact PREVIEW components.\n *\n * Deliberately does NOT extend the heavy {@link BaseArtifactViewerPluginComponent} — previews\n * are lightweight, read-only, and have no toolbar / feedback / tabs / snapshot contract. They\n * render a compact representation of the artifact inside a conversation message card.\n *\n * Content resolution (inline data-URL vs. file-storage download URL) is shared with the full\n * viewers via the injected {@link ArtifactFileService}, so previews and viewers load bytes the\n * same way. Subclasses call {@link resolveContentUrl} in their `ngOnInit`.\n */\n@Component({\n standalone: false,\n template: '',\n})\nexport abstract class BaseArtifactPreviewComponent extends BaseAngularComponent implements IArtifactPreviewComponent {\n /** The artifact version to preview. Set by the resolver before the component renders. */\n @Input() artifactVersion!: MJArtifactVersionEntity;\n\n /** Shared service used by both previews and full viewers to fetch file-mode download URLs. */\n protected readonly fileService = inject(ArtifactFileService);\n\n /**\n * True until {@link resolveContentUrl} has settled, one way or the other.\n *\n * Owned here, not per subclass, so that every media preview has a loading state by\n * construction. Each preview used to carry its own copy of this flag — or, before that, no\n * loading branch at all, which rendered nothing while the URL resolved and made a generated\n * image read as \"never arrived\". `resolveContentUrl` is async even for inline content, and\n * for `ContentMode === 'File'` it is a network round trip before the media fetch even starts.\n */\n public isLoading = true;\n\n /**\n * Resolve a URL to bind to a media element (`<img>`, `<video>`, `<audio>`):\n * - `ContentMode === 'File'` → fetch a pre-authenticated download URL from MJStorage.\n * - otherwise (`'Text'`) → `Content` is already a `data:<mime>;base64,…` URI; bind directly.\n *\n * Returns `null` when there is no usable content (caller should show its own empty/error state).\n * Clears {@link isLoading} on every exit, including a throw, so a subclass cannot leave the\n * indicator spinning beside an error.\n */\n protected async resolveContentUrl(): Promise<string | null> {\n try {\n return await this.resolveContentUrlCore();\n } finally {\n this.isLoading = false;\n }\n }\n\n private async resolveContentUrlCore(): Promise<string | null> {\n const version = this.artifactVersion;\n if (!version?.ID) {\n return null;\n }\n\n if (version.ContentMode === 'File') {\n return this.fileService.getDownloadUrl(version.ID);\n }\n\n const content = version.Content;\n return content && content.length > 0 ? content : null;\n }\n}\n"]}
@@ -11,6 +11,12 @@ import * as i0 from "@angular/core";
11
11
  * fixed width) is the only width constraint, so the aspect ratio is preserved and portrait images
12
12
  * aren't distorted; the height cap is the single lever for overall size. Kept in sync with the video
13
13
  * preview's cap so both visual media previews stay visually consistent.
14
+ *
15
+ * Loading: the component has THREE states, not two. `resolveContentUrl()` is async, and an inline
16
+ * `data:` URI — what MJ stores whenever no file storage account is configured — can be several MB
17
+ * that the browser still has to decode after the src is assigned. With only `error` and `loaded`
18
+ * branches the element rendered nothing at all through both windows, so a generated image read as
19
+ * "never arrived" and then appeared unannounced seconds later.
14
20
  */
15
21
  export declare class ImageArtifactPreviewComponent extends BaseArtifactPreviewComponent implements OnInit {
16
22
  private readonly cdr;
@@ -18,9 +24,16 @@ export declare class ImageArtifactPreviewComponent extends BaseArtifactPreviewCo
18
24
  imageUrl: string;
19
25
  /** Non-empty hides the image and shows a compact error line. */
20
26
  errorMessage: string;
27
+ /**
28
+ * Has the browser painted the image? Distinct from the base class's {@link isLoading}, which
29
+ * covers URL resolution: for a multi-megabyte inline `data:` URI the decode after `src` is
30
+ * assigned is what the user actually waits on, so the indicator holds until the `load` event.
31
+ */
32
+ imagePainted: boolean;
21
33
  /** Accessible alt text — prefers a descriptive name over a raw filename. */
22
34
  get altText(): string;
23
35
  ngOnInit(): Promise<void>;
36
+ onImageLoaded(): void;
24
37
  onImageError(): void;
25
38
  static ɵfac: i0.ɵɵFactoryDeclaration<ImageArtifactPreviewComponent, never>;
26
39
  static ɵcmp: i0.ɵɵComponentDeclaration<ImageArtifactPreviewComponent, "mj-image-artifact-preview", never, {}, {}, never, never, false, never>;
@@ -1 +1 @@
1
- {"version":3,"file":"image-artifact-preview.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/previews/image-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;;AAEjF;;;;;;;;;;GAUG;AACH,qBA8Ca,6BAA8B,SAAQ,4BAA6B,YAAW,MAAM;IAC7F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IAEjD,oFAAoF;IAC7E,QAAQ,SAAM;IAErB,gEAAgE;IACzD,YAAY,SAAM;IAEzB,4EAA4E;IAC5E,IAAW,OAAO,IAAI,MAAM,CAE3B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAcxB,YAAY,IAAI,IAAI;yCA5BlB,6BAA6B;2CAA7B,6BAA6B;CAiCzC"}
1
+ {"version":3,"file":"image-artifact-preview.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/previews/image-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;;AAEjF;;;;;;;;;;;;;;;;GAgBG;AACH,qBAmEa,6BAA8B,SAAQ,4BAA6B,YAAW,MAAM;IAC7F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IAEjD,oFAAoF;IAC7E,QAAQ,SAAM;IAErB,gEAAgE;IACzD,YAAY,SAAM;IAEzB;;;;OAIG;IACI,YAAY,UAAS;IAE5B,4EAA4E;IAC5E,IAAW,OAAO,IAAI,MAAM,CAE3B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAcxB,aAAa,IAAI,IAAI;IAKrB,YAAY,IAAI,IAAI;yCAxClB,6BAA6B;2CAA7B,6BAA6B;CA4CzC"}
@@ -1,9 +1,10 @@
1
1
  import { ChangeDetectorRef, Component, inject } from '@angular/core';
2
2
  import { BaseArtifactPreviewComponent } from './base-artifact-preview.component';
3
3
  import * as i0 from "@angular/core";
4
+ import * as i1 from "@memberjunction/ng-shared-generic";
4
5
  function ImageArtifactPreviewComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
5
6
  i0.ɵɵelementStart(0, "div", 0);
6
- i0.ɵɵelement(1, "i", 2);
7
+ i0.ɵɵelement(1, "i", 1);
7
8
  i0.ɵɵelementStart(2, "span");
8
9
  i0.ɵɵtext(3);
9
10
  i0.ɵɵelementEnd()();
@@ -12,15 +13,28 @@ function ImageArtifactPreviewComponent_Conditional_0_Template(rf, ctx) { if (rf
12
13
  i0.ɵɵadvance(3);
13
14
  i0.ɵɵtextInterpolate(ctx_r0.errorMessage);
14
15
  } }
15
- function ImageArtifactPreviewComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
16
+ function ImageArtifactPreviewComponent_Conditional_1_Conditional_0_Template(rf, ctx) { if (rf & 1) {
17
+ i0.ɵɵelement(0, "mj-loading", 2);
18
+ } }
19
+ function ImageArtifactPreviewComponent_Conditional_1_Conditional_1_Template(rf, ctx) { if (rf & 1) {
16
20
  const _r2 = i0.ɵɵgetCurrentView();
17
- i0.ɵɵelementStart(0, "img", 3);
18
- i0.ɵɵlistener("error", function ImageArtifactPreviewComponent_Conditional_1_Template_img_error_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onImageError()); });
21
+ i0.ɵɵelementStart(0, "img", 4);
22
+ i0.ɵɵlistener("load", function ImageArtifactPreviewComponent_Conditional_1_Conditional_1_Template_img_load_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.onImageLoaded()); })("error", function ImageArtifactPreviewComponent_Conditional_1_Conditional_1_Template_img_error_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.onImageError()); });
19
23
  i0.ɵɵelementEnd();
20
24
  } if (rf & 2) {
21
- const ctx_r0 = i0.ɵɵnextContext();
25
+ const ctx_r0 = i0.ɵɵnextContext(2);
26
+ i0.ɵɵclassProp("image-preview__img--hidden", !ctx_r0.imagePainted);
22
27
  i0.ɵɵproperty("src", ctx_r0.imageUrl, i0.ɵɵsanitizeUrl)("alt", ctx_r0.altText);
23
28
  } }
29
+ function ImageArtifactPreviewComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
30
+ i0.ɵɵconditionalCreate(0, ImageArtifactPreviewComponent_Conditional_1_Conditional_0_Template, 1, 0, "mj-loading", 2);
31
+ i0.ɵɵconditionalCreate(1, ImageArtifactPreviewComponent_Conditional_1_Conditional_1_Template, 1, 4, "img", 3);
32
+ } if (rf & 2) {
33
+ const ctx_r0 = i0.ɵɵnextContext();
34
+ i0.ɵɵconditional(ctx_r0.isLoading || !ctx_r0.imagePainted ? 0 : -1);
35
+ i0.ɵɵadvance();
36
+ i0.ɵɵconditional(ctx_r0.imageUrl ? 1 : -1);
37
+ } }
24
38
  /**
25
39
  * Inline preview for image artifacts. Renders a single contained `<img>` (max 140px tall) inside
26
40
  * the conversation message card. Read-only — no toolbar, no zoom, no download. Clicking it bubbles
@@ -31,6 +45,12 @@ function ImageArtifactPreviewComponent_Conditional_1_Template(rf, ctx) { if (rf
31
45
  * fixed width) is the only width constraint, so the aspect ratio is preserved and portrait images
32
46
  * aren't distorted; the height cap is the single lever for overall size. Kept in sync with the video
33
47
  * preview's cap so both visual media previews stay visually consistent.
48
+ *
49
+ * Loading: the component has THREE states, not two. `resolveContentUrl()` is async, and an inline
50
+ * `data:` URI — what MJ stores whenever no file storage account is configured — can be several MB
51
+ * that the browser still has to decode after the src is assigned. With only `error` and `loaded`
52
+ * branches the element rendered nothing at all through both windows, so a generated image read as
53
+ * "never arrived" and then appeared unannounced seconds later.
34
54
  */
35
55
  export class ImageArtifactPreviewComponent extends BaseArtifactPreviewComponent {
36
56
  cdr = inject(ChangeDetectorRef);
@@ -38,6 +58,12 @@ export class ImageArtifactPreviewComponent extends BaseArtifactPreviewComponent
38
58
  imageUrl = '';
39
59
  /** Non-empty hides the image and shows a compact error line. */
40
60
  errorMessage = '';
61
+ /**
62
+ * Has the browser painted the image? Distinct from the base class's {@link isLoading}, which
63
+ * covers URL resolution: for a multi-megabyte inline `data:` URI the decode after `src` is
64
+ * assigned is what the user actually waits on, so the indicator holds until the `load` event.
65
+ */
66
+ imagePainted = false;
41
67
  /** Accessible alt text — prefers a descriptive name over a raw filename. */
42
68
  get altText() {
43
69
  return this.artifactVersion?.Name || this.artifactVersion?.FileName || 'Image artifact';
@@ -57,17 +83,20 @@ export class ImageArtifactPreviewComponent extends BaseArtifactPreviewComponent
57
83
  }
58
84
  this.cdr.markForCheck();
59
85
  }
86
+ onImageLoaded() {
87
+ this.imagePainted = true;
88
+ this.cdr.markForCheck();
89
+ }
60
90
  onImageError() {
61
91
  this.errorMessage = 'Image could not be displayed.';
62
- this.imageUrl = '';
63
92
  this.cdr.markForCheck();
64
93
  }
65
94
  static ɵfac = /*@__PURE__*/ (() => { let ɵImageArtifactPreviewComponent_BaseFactory; return function ImageArtifactPreviewComponent_Factory(__ngFactoryType__) { return (ɵImageArtifactPreviewComponent_BaseFactory || (ɵImageArtifactPreviewComponent_BaseFactory = i0.ɵɵgetInheritedFactory(ImageArtifactPreviewComponent)))(__ngFactoryType__ || ImageArtifactPreviewComponent); }; })();
66
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ImageArtifactPreviewComponent, selectors: [["mj-image-artifact-preview"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 1, consts: [[1, "image-preview", "image-preview--error"], [1, "image-preview__img", 3, "src", "alt"], [1, "fa-solid", "fa-image"], [1, "image-preview__img", 3, "error", "src", "alt"]], template: function ImageArtifactPreviewComponent_Template(rf, ctx) { if (rf & 1) {
67
- i0.ɵɵconditionalCreate(0, ImageArtifactPreviewComponent_Conditional_0_Template, 4, 1, "div", 0)(1, ImageArtifactPreviewComponent_Conditional_1_Template, 1, 2, "img", 1);
95
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ImageArtifactPreviewComponent, selectors: [["mj-image-artifact-preview"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 1, consts: [[1, "image-preview", "image-preview--error"], [1, "fa-solid", "fa-image"], ["text", "Loading image...", "size", "small"], [1, "image-preview__img", 3, "image-preview__img--hidden", "src", "alt"], [1, "image-preview__img", 3, "load", "error", "src", "alt"]], template: function ImageArtifactPreviewComponent_Template(rf, ctx) { if (rf & 1) {
96
+ i0.ɵɵconditionalCreate(0, ImageArtifactPreviewComponent_Conditional_0_Template, 4, 1, "div", 0)(1, ImageArtifactPreviewComponent_Conditional_1_Template, 2, 2);
68
97
  } if (rf & 2) {
69
- i0.ɵɵconditional(ctx.errorMessage ? 0 : ctx.imageUrl ? 1 : -1);
70
- } }, styles: ["[_nghost-%COMP%] {\n display: block;\n }\n\n .image-preview__img[_ngcontent-%COMP%] {\n display: block;\n max-width: 100%;\n max-height: 140px;\n width: auto;\n height: auto;\n object-fit: contain;\n border-radius: 6px;\n background: var(--mj-bg-surface-sunken);\n }\n\n .image-preview--error[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }"] });
98
+ i0.ɵɵconditional(ctx.errorMessage ? 0 : 1);
99
+ } }, dependencies: [i1.LoadingComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n \n\n\n\n position: relative;\n }\n\n .image-preview__img[_ngcontent-%COMP%] {\n display: block;\n max-width: 100%;\n max-height: 140px;\n width: auto;\n height: auto;\n object-fit: contain;\n border-radius: 6px;\n background: var(--mj-bg-surface-sunken);\n }\n\n \n\n\n .image-preview__img--hidden[_ngcontent-%COMP%] {\n opacity: 0;\n position: absolute;\n pointer-events: none;\n }\n\n .image-preview--error[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }"] });
71
100
  }
72
101
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ImageArtifactPreviewComponent, [{
73
102
  type: Component,
@@ -77,15 +106,22 @@ export class ImageArtifactPreviewComponent extends BaseArtifactPreviewComponent
77
106
  <i class="fa-solid fa-image"></i>
78
107
  <span>{{ errorMessage }}</span>
79
108
  </div>
80
- } @else if (imageUrl) {
81
- <img
82
- class="image-preview__img"
83
- [src]="imageUrl"
84
- [alt]="altText"
85
- (error)="onImageError()"
86
- />
109
+ } @else {
110
+ @if (isLoading || !imagePainted) {
111
+ <mj-loading text="Loading image..." size="small"></mj-loading>
112
+ }
113
+ @if (imageUrl) {
114
+ <img
115
+ class="image-preview__img"
116
+ [class.image-preview__img--hidden]="!imagePainted"
117
+ [src]="imageUrl"
118
+ [alt]="altText"
119
+ (load)="onImageLoaded()"
120
+ (error)="onImageError()"
121
+ />
122
+ }
87
123
  }
88
- `, styles: ["\n :host {\n display: block;\n }\n\n .image-preview__img {\n display: block;\n max-width: 100%;\n max-height: 140px;\n width: auto;\n height: auto;\n object-fit: contain;\n border-radius: 6px;\n background: var(--mj-bg-surface-sunken);\n }\n\n .image-preview--error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n "] }]
124
+ `, styles: ["\n :host {\n display: block;\n /* Containing block for the pending <img>, which is position:absolute so it can be\n painted (and therefore decoded) without reserving layout. Without this it\n positions against whatever ancestor happens to be positioned \u2014 in the\n conversation, potentially the scroll container. */\n position: relative;\n }\n\n .image-preview__img {\n display: block;\n max-width: 100%;\n max-height: 140px;\n width: auto;\n height: auto;\n object-fit: contain;\n border-radius: 6px;\n background: var(--mj-bg-surface-sunken);\n }\n\n /* Same decode-while-hidden treatment as ImageArtifactViewerComponent's\n .image-viewer__img--hidden, for the same reason: the element must stay in the paint\n tree so the browser decodes it, which display:none would prevent. */\n .image-preview__img--hidden {\n opacity: 0;\n position: absolute;\n pointer-events: none;\n }\n\n .image-preview--error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n "] }]
89
125
  }], null, null); })();
90
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ImageArtifactPreviewComponent, { className: "ImageArtifactPreviewComponent", filePath: "src/lib/components/previews/image-artifact-preview.component.ts", lineNumber: 61 }); })();
126
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ImageArtifactPreviewComponent, { className: "ImageArtifactPreviewComponent", filePath: "src/lib/components/previews/image-artifact-preview.component.ts", lineNumber: 88 }); })();
91
127
  //# sourceMappingURL=image-artifact-preview.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-artifact-preview.component.js","sourceRoot":"","sources":["../../../../src/lib/components/previews/image-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;;;IAkBrE,8BAAgD;IAC5C,uBAAiC;IACjC,4BAAM;IAAA,YAAkB;IAC5B,AAD4B,iBAAO,EAC7B;;;IADI,eAAkB;IAAlB,yCAAkB;;;;IAG5B,8BAKE;IADE,8LAAS,qBAAc,KAAC;IAJ5B,iBAKE;;;IAFE,AADA,uDAAgB,uBACD;;AAxB/B;;;;;;;;;;GAUG;AA+CH,MAAM,OAAO,6BAA8B,SAAQ,4BAA4B;IAC1D,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,oFAAoF;IAC7E,QAAQ,GAAG,EAAE,CAAC;IAErB,gEAAgE;IACzD,YAAY,GAAG,EAAE,CAAC;IAEzB,4EAA4E;IAC5E,IAAW,OAAO;QACd,OAAO,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,gBAAgB,CAAC;IAC5F,CAAC;IAED,KAAK,CAAC,QAAQ;QACV,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3C,IAAI,GAAG,EAAE,CAAC;gBACN,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,YAAY,GAAG,mBAAmB,CAAC;YAC5C,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,IAAI,CAAC,YAAY,GAAG,uBAAuB,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAEM,YAAY;QACf,IAAI,CAAC,YAAY,GAAG,+BAA+B,CAAC;QACpD,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QACnB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;iSAhCQ,6BAA6B,yBAA7B,6BAA6B;6DAA7B,6BAA6B;YArChC,AALF,+FAAoB,yEAKG;;YALvB,8DAYC;;;iFA8BI,6BAA6B;cA9CzC,SAAS;6BACM,KAAK,YACP,2BAA2B,YAC3B;;;;;;;;;;;;;;KAcT;;kFA6BQ,6BAA6B","sourcesContent":["import { ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';\nimport { BaseArtifactPreviewComponent } from './base-artifact-preview.component';\n\n/**\n * Inline preview for image artifacts. Renders a single contained `<img>` (max 140px tall) inside\n * the conversation message card. Read-only — no toolbar, no zoom, no download. Clicking it bubbles\n * up through the card's clickable wrapper to open the full-size image viewer.\n *\n * Height note: this is a *thumbnail*, not the full view — kept deliberately compact (140px) so a\n * message with an image artifact doesn't dominate the conversation. `max-width: 100%` (rather than a\n * fixed width) is the only width constraint, so the aspect ratio is preserved and portrait images\n * aren't distorted; the height cap is the single lever for overall size. Kept in sync with the video\n * preview's cap so both visual media previews stay visually consistent.\n */\n@Component({\n standalone: false,\n selector: 'mj-image-artifact-preview',\n template: `\n @if (errorMessage) {\n <div class=\"image-preview image-preview--error\">\n <i class=\"fa-solid fa-image\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else if (imageUrl) {\n <img\n class=\"image-preview__img\"\n [src]=\"imageUrl\"\n [alt]=\"altText\"\n (error)=\"onImageError()\"\n />\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n .image-preview__img {\n display: block;\n max-width: 100%;\n max-height: 140px;\n width: auto;\n height: auto;\n object-fit: contain;\n border-radius: 6px;\n background: var(--mj-bg-surface-sunken);\n }\n\n .image-preview--error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n `,\n ],\n})\nexport class ImageArtifactPreviewComponent extends BaseArtifactPreviewComponent implements OnInit {\n private readonly cdr = inject(ChangeDetectorRef);\n\n /** Resolved URL bound to `<img src>` — data URI (inline) or pre-auth URL (file). */\n public imageUrl = '';\n\n /** Non-empty hides the image and shows a compact error line. */\n public errorMessage = '';\n\n /** Accessible alt text — prefers a descriptive name over a raw filename. */\n public get altText(): string {\n return this.artifactVersion?.Name || this.artifactVersion?.FileName || 'Image artifact';\n }\n\n async ngOnInit(): Promise<void> {\n try {\n const url = await this.resolveContentUrl();\n if (url) {\n this.imageUrl = url;\n } else {\n this.errorMessage = 'No image content.';\n }\n } catch {\n this.errorMessage = 'Could not load image.';\n }\n this.cdr.markForCheck();\n }\n\n public onImageError(): void {\n this.errorMessage = 'Image could not be displayed.';\n this.imageUrl = '';\n this.cdr.markForCheck();\n }\n}\n"]}
1
+ {"version":3,"file":"image-artifact-preview.component.js","sourceRoot":"","sources":["../../../../src/lib/components/previews/image-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;;;;IAwBrE,8BAAgD;IAC5C,uBAAiC;IACjC,4BAAM;IAAA,YAAkB;IAC5B,AAD4B,iBAAO,EAC7B;;;IADI,eAAkB;IAAlB,yCAAkB;;;IAIxB,gCAA8D;;;;IAG9D,8BAOE;IADE,AADA,2MAAQ,sBAAe,KAAC,gMACf,qBAAc,KAAC;IAN5B,iBAOE;;;IALE,kEAAkD;IAElD,AADA,uDAAgB,uBACD;;;IARvB,oHAAkC;IAGlC,6GAAgB;;;IAHhB,mEAEC;IACD,cASC;IATD,0CASC;;AAvCb;;;;;;;;;;;;;;;;GAgBG;AAoEH,MAAM,OAAO,6BAA8B,SAAQ,4BAA4B;IAC1D,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,oFAAoF;IAC7E,QAAQ,GAAG,EAAE,CAAC;IAErB,gEAAgE;IACzD,YAAY,GAAG,EAAE,CAAC;IAEzB;;;;OAIG;IACI,YAAY,GAAG,KAAK,CAAC;IAE5B,4EAA4E;IAC5E,IAAW,OAAO;QACd,OAAO,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,gBAAgB,CAAC;IAC5F,CAAC;IAED,KAAK,CAAC,QAAQ;QACV,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3C,IAAI,GAAG,EAAE,CAAC;gBACN,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,YAAY,GAAG,mBAAmB,CAAC;YAC5C,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,IAAI,CAAC,YAAY,GAAG,uBAAuB,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAEM,aAAa;QAChB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAEM,YAAY;QACf,IAAI,CAAC,YAAY,GAAG,+BAA+B,CAAC;QACpD,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;iSA3CQ,6BAA6B,yBAA7B,6BAA6B;6DAA7B,6BAA6B;YA1DhC,AALF,+FAAoB,+DAKX;;YALT,0CAmBC;;;iFA4CI,6BAA6B;cAnEzC,SAAS;6BACM,KAAK,YACP,2BAA2B,YAC3B;;;;;;;;;;;;;;;;;;;;;KAqBT;;kFA2CQ,6BAA6B","sourcesContent":["import { ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';\nimport { BaseArtifactPreviewComponent } from './base-artifact-preview.component';\n\n/**\n * Inline preview for image artifacts. Renders a single contained `<img>` (max 140px tall) inside\n * the conversation message card. Read-only — no toolbar, no zoom, no download. Clicking it bubbles\n * up through the card's clickable wrapper to open the full-size image viewer.\n *\n * Height note: this is a *thumbnail*, not the full view — kept deliberately compact (140px) so a\n * message with an image artifact doesn't dominate the conversation. `max-width: 100%` (rather than a\n * fixed width) is the only width constraint, so the aspect ratio is preserved and portrait images\n * aren't distorted; the height cap is the single lever for overall size. Kept in sync with the video\n * preview's cap so both visual media previews stay visually consistent.\n *\n * Loading: the component has THREE states, not two. `resolveContentUrl()` is async, and an inline\n * `data:` URI — what MJ stores whenever no file storage account is configured — can be several MB\n * that the browser still has to decode after the src is assigned. With only `error` and `loaded`\n * branches the element rendered nothing at all through both windows, so a generated image read as\n * \"never arrived\" and then appeared unannounced seconds later.\n */\n@Component({\n standalone: false,\n selector: 'mj-image-artifact-preview',\n template: `\n @if (errorMessage) {\n <div class=\"image-preview image-preview--error\">\n <i class=\"fa-solid fa-image\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else {\n @if (isLoading || !imagePainted) {\n <mj-loading text=\"Loading image...\" size=\"small\"></mj-loading>\n }\n @if (imageUrl) {\n <img\n class=\"image-preview__img\"\n [class.image-preview__img--hidden]=\"!imagePainted\"\n [src]=\"imageUrl\"\n [alt]=\"altText\"\n (load)=\"onImageLoaded()\"\n (error)=\"onImageError()\"\n />\n }\n }\n `,\n styles: [\n `\n :host {\n display: block;\n /* Containing block for the pending <img>, which is position:absolute so it can be\n painted (and therefore decoded) without reserving layout. Without this it\n positions against whatever ancestor happens to be positioned — in the\n conversation, potentially the scroll container. */\n position: relative;\n }\n\n .image-preview__img {\n display: block;\n max-width: 100%;\n max-height: 140px;\n width: auto;\n height: auto;\n object-fit: contain;\n border-radius: 6px;\n background: var(--mj-bg-surface-sunken);\n }\n\n /* Same decode-while-hidden treatment as ImageArtifactViewerComponent's\n .image-viewer__img--hidden, for the same reason: the element must stay in the paint\n tree so the browser decodes it, which display:none would prevent. */\n .image-preview__img--hidden {\n opacity: 0;\n position: absolute;\n pointer-events: none;\n }\n\n .image-preview--error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n `,\n ],\n})\nexport class ImageArtifactPreviewComponent extends BaseArtifactPreviewComponent implements OnInit {\n private readonly cdr = inject(ChangeDetectorRef);\n\n /** Resolved URL bound to `<img src>` — data URI (inline) or pre-auth URL (file). */\n public imageUrl = '';\n\n /** Non-empty hides the image and shows a compact error line. */\n public errorMessage = '';\n\n /**\n * Has the browser painted the image? Distinct from the base class's {@link isLoading}, which\n * covers URL resolution: for a multi-megabyte inline `data:` URI the decode after `src` is\n * assigned is what the user actually waits on, so the indicator holds until the `load` event.\n */\n public imagePainted = false;\n\n /** Accessible alt text — prefers a descriptive name over a raw filename. */\n public get altText(): string {\n return this.artifactVersion?.Name || this.artifactVersion?.FileName || 'Image artifact';\n }\n\n async ngOnInit(): Promise<void> {\n try {\n const url = await this.resolveContentUrl();\n if (url) {\n this.imageUrl = url;\n } else {\n this.errorMessage = 'No image content.';\n }\n } catch {\n this.errorMessage = 'Could not load image.';\n }\n this.cdr.markForCheck();\n }\n\n public onImageLoaded(): void {\n this.imagePainted = true;\n this.cdr.markForCheck();\n }\n\n public onImageError(): void {\n this.errorMessage = 'Image could not be displayed.';\n this.cdr.markForCheck();\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"video-artifact-preview.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/previews/video-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;;AAEjF;;;;;;;;;GASG;AACH,qBAqDa,6BAA8B,SAAQ,4BAA6B,YAAW,MAAM;IAC7F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IAEjD,qGAAqG;IAC9F,QAAQ,SAAM;IAErB,iEAAiE;IAC1D,YAAY,SAAM;IAEzB;;;OAGG;IACI,WAAW,EAAE,UAAU,EAAE,CAAM;IAEtC,mGAAmG;IACnG,OAAO,CAAC,WAAW;IAcb,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAcxB,YAAY,IAAI,IAAI;IAM3B,yFAAyF;IAClF,YAAY,IAAI,IAAI;yCAnDlB,6BAA6B;2CAA7B,6BAA6B;CAsDzC"}
1
+ {"version":3,"file":"video-artifact-preview.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/previews/video-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;;AAEjF;;;;;;;;;GASG;AACH,qBAuDa,6BAA8B,SAAQ,4BAA6B,YAAW,MAAM;IAC7F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IAEjD,qGAAqG;IAC9F,QAAQ,SAAM;IAGrB,iEAAiE;IAC1D,YAAY,SAAM;IAEzB;;;OAGG;IACI,WAAW,EAAE,UAAU,EAAE,CAAM;IAEtC,mGAAmG;IACnG,OAAO,CAAC,WAAW;IAcb,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAcxB,YAAY,IAAI,IAAI;IAM3B,yFAAyF;IAClF,YAAY,IAAI,IAAI;yCApDlB,6BAA6B;2CAA7B,6BAA6B;CAuDzC"}
@@ -1,10 +1,11 @@
1
1
  import { ChangeDetectorRef, Component, inject } from '@angular/core';
2
2
  import { BaseArtifactPreviewComponent } from './base-artifact-preview.component';
3
3
  import * as i0 from "@angular/core";
4
- import * as i1 from "@memberjunction/ng-media-player";
4
+ import * as i1 from "@memberjunction/ng-shared-generic";
5
+ import * as i2 from "@memberjunction/ng-media-player";
5
6
  function VideoArtifactPreviewComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
6
7
  i0.ɵɵelementStart(0, "div", 0);
7
- i0.ɵɵelement(1, "i", 2);
8
+ i0.ɵɵelement(1, "i", 3);
8
9
  i0.ɵɵelementStart(2, "span");
9
10
  i0.ɵɵtext(3);
10
11
  i0.ɵɵelementEnd()();
@@ -14,9 +15,12 @@ function VideoArtifactPreviewComponent_Conditional_0_Template(rf, ctx) { if (rf
14
15
  i0.ɵɵtextInterpolate(ctx_r0.errorMessage);
15
16
  } }
16
17
  function VideoArtifactPreviewComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
18
+ i0.ɵɵelement(0, "mj-loading", 1);
19
+ } }
20
+ function VideoArtifactPreviewComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
17
21
  const _r2 = i0.ɵɵgetCurrentView();
18
- i0.ɵɵelementStart(0, "mj-media-player", 3);
19
- i0.ɵɵlistener("click", function VideoArtifactPreviewComponent_Conditional_1_Template_mj_media_player_click_0_listener($event) { i0.ɵɵrestoreView(_r2); return i0.ɵɵresetView($event.stopPropagation()); })("Ended", function VideoArtifactPreviewComponent_Conditional_1_Template_mj_media_player_Ended_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onMediaEnded()); });
22
+ i0.ɵɵelementStart(0, "mj-media-player", 4);
23
+ i0.ɵɵlistener("click", function VideoArtifactPreviewComponent_Conditional_2_Template_mj_media_player_click_0_listener($event) { return $event.stopPropagation(); })("Ended", function VideoArtifactPreviewComponent_Conditional_2_Template_mj_media_player_Ended_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onMediaEnded()); });
20
24
  i0.ɵɵelementEnd();
21
25
  } if (rf & 2) {
22
26
  const ctx_r0 = i0.ɵɵnextContext();
@@ -82,11 +86,11 @@ export class VideoArtifactPreviewComponent extends BaseArtifactPreviewComponent
82
86
  // Playback finished — nothing to persist for an inline preview.
83
87
  }
84
88
  static ɵfac = /*@__PURE__*/ (() => { let ɵVideoArtifactPreviewComponent_BaseFactory; return function VideoArtifactPreviewComponent_Factory(__ngFactoryType__) { return (ɵVideoArtifactPreviewComponent_BaseFactory || (ɵVideoArtifactPreviewComponent_BaseFactory = i0.ɵɵgetInheritedFactory(VideoArtifactPreviewComponent)))(__ngFactoryType__ || VideoArtifactPreviewComponent); }; })();
85
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: VideoArtifactPreviewComponent, selectors: [["mj-video-artifact-preview"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 1, consts: [[1, "video-preview", "video-preview--error"], [1, "video-preview__player", 3, "Tracks", "ShowTranscript", "ShowTranscriptToggle", "ShowWaveform", "ShowSpeedControl", "ShowSkipControls", "ShowVolume"], [1, "fa-solid", "fa-film"], [1, "video-preview__player", 3, "click", "Ended", "Tracks", "ShowTranscript", "ShowTranscriptToggle", "ShowWaveform", "ShowSpeedControl", "ShowSkipControls", "ShowVolume"]], template: function VideoArtifactPreviewComponent_Template(rf, ctx) { if (rf & 1) {
86
- i0.ɵɵconditionalCreate(0, VideoArtifactPreviewComponent_Conditional_0_Template, 4, 1, "div", 0)(1, VideoArtifactPreviewComponent_Conditional_1_Template, 1, 7, "mj-media-player", 1);
89
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: VideoArtifactPreviewComponent, selectors: [["mj-video-artifact-preview"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 1, consts: [[1, "video-preview", "video-preview--error"], ["text", "Loading video...", "size", "small"], [1, "video-preview__player", 3, "Tracks", "ShowTranscript", "ShowTranscriptToggle", "ShowWaveform", "ShowSpeedControl", "ShowSkipControls", "ShowVolume"], [1, "fa-solid", "fa-film"], [1, "video-preview__player", 3, "click", "Ended", "Tracks", "ShowTranscript", "ShowTranscriptToggle", "ShowWaveform", "ShowSpeedControl", "ShowSkipControls", "ShowVolume"]], template: function VideoArtifactPreviewComponent_Template(rf, ctx) { if (rf & 1) {
90
+ i0.ɵɵconditionalCreate(0, VideoArtifactPreviewComponent_Conditional_0_Template, 4, 1, "div", 0)(1, VideoArtifactPreviewComponent_Conditional_1_Template, 1, 0, "mj-loading", 1)(2, VideoArtifactPreviewComponent_Conditional_2_Template, 1, 7, "mj-media-player", 2);
87
91
  } if (rf & 2) {
88
- i0.ɵɵconditional(ctx.errorMessage ? 0 : ctx.videoUrl ? 1 : -1);
89
- } }, dependencies: [i1.MJMediaPlayerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n }\n\n \n\n\n .video-preview__player[_ngcontent-%COMP%] {\n display: block;\n width: 100%;\n max-width: 100%;\n max-height: 140px;\n border-radius: 6px;\n overflow: hidden;\n background: var(--mj-bg-surface-sunken);\n }\n\n .video-preview--error[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }"] });
92
+ i0.ɵɵconditional(ctx.errorMessage ? 0 : ctx.isLoading ? 1 : ctx.videoUrl ? 2 : -1);
93
+ } }, dependencies: [i1.LoadingComponent, i2.MJMediaPlayerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n }\n\n \n\n .video-preview__player[_ngcontent-%COMP%] {\n display: block;\n width: 100%;\n max-width: 100%;\n max-height: 140px;\n border-radius: 6px;\n overflow: hidden;\n background: var(--mj-bg-surface-sunken);\n }\n\n .video-preview--error[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }"] });
90
94
  }
91
95
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(VideoArtifactPreviewComponent, [{
92
96
  type: Component,
@@ -96,6 +100,8 @@ export class VideoArtifactPreviewComponent extends BaseArtifactPreviewComponent
96
100
  <i class="fa-solid fa-film"></i>
97
101
  <span>{{ errorMessage }}</span>
98
102
  </div>
103
+ } @else if (isLoading) {
104
+ <mj-loading text="Loading video..." size="small"></mj-loading>
99
105
  } @else if (videoUrl) {
100
106
  <mj-media-player
101
107
  class="video-preview__player"
@@ -112,5 +118,5 @@ export class VideoArtifactPreviewComponent extends BaseArtifactPreviewComponent
112
118
  }
113
119
  `, styles: ["\n :host {\n display: block;\n }\n\n /* Compact thumbnail \u2014 cap the height so the video surface stays consistent with the\n image preview (140px) and neither dominates the conversation card. */\n .video-preview__player {\n display: block;\n width: 100%;\n max-width: 100%;\n max-height: 140px;\n border-radius: 6px;\n overflow: hidden;\n background: var(--mj-bg-surface-sunken);\n }\n\n .video-preview--error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n "] }]
114
120
  }], null, null); })();
115
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(VideoArtifactPreviewComponent, { className: "VideoArtifactPreviewComponent", filePath: "src/lib/components/previews/video-artifact-preview.component.ts", lineNumber: 68 }); })();
121
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(VideoArtifactPreviewComponent, { className: "VideoArtifactPreviewComponent", filePath: "src/lib/components/previews/video-artifact-preview.component.ts", lineNumber: 70 }); })();
116
122
  //# sourceMappingURL=video-artifact-preview.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"video-artifact-preview.component.js","sourceRoot":"","sources":["../../../../src/lib/components/previews/video-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAE7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;;;;IAiBrE,8BAAgD;IAC5C,uBAAgC;IAChC,4BAAM;IAAA,YAAkB;IAC5B,AAD4B,iBAAO,EAC7B;;;IADI,eAAkB;IAAlB,yCAAkB;;;;IAG5B,0CAWC;IADG,AADA,6KAAS,wBAAwB,KAAC,6LACzB,qBAAc,KAAC;IAC3B,iBAAkB;;;IAHf,AADA,AADA,AADA,AADA,AADA,AADA,2CAAsB,yBACE,+BACM,uBACR,2BACI,2BACA,qBACN;;AA5BpC;;;;;;;;;GASG;AAsDH,MAAM,OAAO,6BAA8B,SAAQ,4BAA4B;IAC1D,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,qGAAqG;IAC9F,QAAQ,GAAG,EAAE,CAAC;IAErB,iEAAiE;IAC1D,YAAY,GAAG,EAAE,CAAC;IAEzB;;;OAGG;IACI,WAAW,GAAiB,EAAE,CAAC;IAEtC,mGAAmG;IAC3F,WAAW,CAAC,GAAW;QAC3B,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,WAAW,GAAG,GAAG;YAClB,CAAC,CAAC;gBACI;oBACI,EAAE,EAAE,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,OAAO;oBACvC,IAAI,EAAE,OAAO;oBACb,GAAG,EAAE,GAAG;oBACR,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,SAAS;iBACxD;aACJ;YACH,CAAC,CAAC,EAAE,CAAC;IACb,CAAC;IAED,KAAK,CAAC,QAAQ;QACV,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3C,IAAI,GAAG,EAAE,CAAC;gBACN,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,YAAY,GAAG,mBAAmB,CAAC;YAC5C,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,IAAI,CAAC,YAAY,GAAG,uBAAuB,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAEM,YAAY;QACf,IAAI,CAAC,YAAY,GAAG,4BAA4B,CAAC;QACjD,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACrB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAED,yFAAyF;IAClF,YAAY;QACf,gEAAgE;IACpE,CAAC;iSArDQ,6BAA6B,yBAA7B,6BAA6B;6DAA7B,6BAA6B;YA5ChC,AALF,+FAAoB,qFAKG;;YALvB,8DAkBC;;;iFA+BI,6BAA6B;cArDzC,SAAS;6BACM,KAAK,YACP,2BAA2B,YAC3B;;;;;;;;;;;;;;;;;;;;KAoBT;;kFA8BQ,6BAA6B","sourcesContent":["import { ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';\nimport { MediaTrack } from '@memberjunction/ng-media-player';\nimport { BaseArtifactPreviewComponent } from './base-artifact-preview.component';\n\n/**\n * Inline preview for video artifacts. Embeds the generic {@link MJMediaPlayerComponent}\n * (`mj-media-player`) in a compact, thumbnail-sized configuration inside the conversation message\n * card. Click events on the player are stopped from propagating so scrubbing / play-pause does\n * NOT open the full-size viewer (the card's other regions still open it).\n *\n * Compact gating: play/pause + scrubber + time (and fullscreen, which is handy for a tiny video\n * surface) are shown. Transcript, waveform, speed, skip and volume chrome are turned OFF so the\n * player reads cleanly at preview size and doesn't crowd the card.\n */\n@Component({\n standalone: false,\n selector: 'mj-video-artifact-preview',\n template: `\n @if (errorMessage) {\n <div class=\"video-preview video-preview--error\">\n <i class=\"fa-solid fa-film\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else if (videoUrl) {\n <mj-media-player\n class=\"video-preview__player\"\n [Tracks]=\"MediaTracks\"\n [ShowTranscript]=\"false\"\n [ShowTranscriptToggle]=\"false\"\n [ShowWaveform]=\"false\"\n [ShowSpeedControl]=\"false\"\n [ShowSkipControls]=\"false\"\n [ShowVolume]=\"false\"\n (click)=\"$event.stopPropagation()\"\n (Ended)=\"onMediaEnded()\"\n ></mj-media-player>\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n /* Compact thumbnail — cap the height so the video surface stays consistent with the\n image preview (140px) and neither dominates the conversation card. */\n .video-preview__player {\n display: block;\n width: 100%;\n max-width: 100%;\n max-height: 140px;\n border-radius: 6px;\n overflow: hidden;\n background: var(--mj-bg-surface-sunken);\n }\n\n .video-preview--error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n `,\n ],\n})\nexport class VideoArtifactPreviewComponent extends BaseArtifactPreviewComponent implements OnInit {\n private readonly cdr = inject(ChangeDetectorRef);\n\n /** Resolved URL — data URI (inline) or pre-auth URL (file). Funneled through {@link setVideoUrl}. */\n public videoUrl = '';\n\n /** Non-empty hides the player and shows a compact error line. */\n public errorMessage = '';\n\n /**\n * Single-track input for the generic {@link MJMediaPlayerComponent}. Rebuilt in lockstep with\n * {@link videoUrl} via {@link setVideoUrl}; `[]` until a URL exists so the player shows its empty state.\n */\n public MediaTracks: MediaTrack[] = [];\n\n /** Set the resolved URL and recompute the single-element {@link MediaTracks} array in lockstep. */\n private setVideoUrl(url: string): void {\n this.videoUrl = url;\n this.MediaTracks = url\n ? [\n {\n Id: this.artifactVersion?.ID ?? 'video',\n Kind: 'video',\n Url: url,\n MimeType: this.artifactVersion?.MimeType ?? undefined,\n },\n ]\n : [];\n }\n\n async ngOnInit(): Promise<void> {\n try {\n const url = await this.resolveContentUrl();\n if (url) {\n this.setVideoUrl(url);\n } else {\n this.errorMessage = 'No video content.';\n }\n } catch {\n this.errorMessage = 'Could not load video.';\n }\n this.cdr.markForCheck();\n }\n\n public onMediaError(): void {\n this.errorMessage = 'Video could not be played.';\n this.setVideoUrl('');\n this.cdr.markForCheck();\n }\n\n /** Fired when the generic player reaches the end of the track. No-op affordance hook. */\n public onMediaEnded(): void {\n // Playback finished — nothing to persist for an inline preview.\n }\n}\n"]}
1
+ {"version":3,"file":"video-artifact-preview.component.js","sourceRoot":"","sources":["../../../../src/lib/components/previews/video-artifact-preview.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAE7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;;;;;IAiBrE,8BAAgD;IAC5C,uBAAgC;IAChC,4BAAM;IAAA,YAAkB;IAC5B,AAD4B,iBAAO,EAC7B;;;IADI,eAAkB;IAAlB,yCAAkB;;;IAG5B,gCAA8D;;;;IAE9D,0CAWC;IADG,AADA,uIAAS,wBAAwB,IAAC,6LACzB,qBAAc,KAAC;IAC3B,iBAAkB;;;IAHf,AADA,AADA,AADA,AADA,AADA,AADA,2CAAsB,yBACE,+BACM,uBACR,2BACI,2BACA,qBACN;;AA9BpC;;;;;;;;;GASG;AAwDH,MAAM,OAAO,6BAA8B,SAAQ,4BAA4B;IAC1D,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,qGAAqG;IAC9F,QAAQ,GAAG,EAAE,CAAC;IAGrB,iEAAiE;IAC1D,YAAY,GAAG,EAAE,CAAC;IAEzB;;;OAGG;IACI,WAAW,GAAiB,EAAE,CAAC;IAEtC,mGAAmG;IAC3F,WAAW,CAAC,GAAW;QAC3B,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,WAAW,GAAG,GAAG;YAClB,CAAC,CAAC;gBACI;oBACI,EAAE,EAAE,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,OAAO;oBACvC,IAAI,EAAE,OAAO;oBACb,GAAG,EAAE,GAAG;oBACR,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,SAAS;iBACxD;aACJ;YACH,CAAC,CAAC,EAAE,CAAC;IACb,CAAC;IAED,KAAK,CAAC,QAAQ;QACV,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3C,IAAI,GAAG,EAAE,CAAC;gBACN,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,YAAY,GAAG,mBAAmB,CAAC;YAC5C,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,IAAI,CAAC,YAAY,GAAG,uBAAuB,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAEM,YAAY;QACf,IAAI,CAAC,YAAY,GAAG,4BAA4B,CAAC;QACjD,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACrB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;IAED,yFAAyF;IAClF,YAAY;QACf,gEAAgE;IACpE,CAAC;iSAtDQ,6BAA6B,yBAA7B,6BAA6B;6DAA7B,6BAA6B;YA5ChC,AAFA,AALF,+FAAoB,gFAKI,qFAED;;YAPvB,kFAoBC;;;iFA+BI,6BAA6B;cAvDzC,SAAS;6BACM,KAAK,YACP,2BAA2B,YAC3B;;;;;;;;;;;;;;;;;;;;;;KAsBT;;kFA8BQ,6BAA6B","sourcesContent":["import { ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';\nimport { MediaTrack } from '@memberjunction/ng-media-player';\nimport { BaseArtifactPreviewComponent } from './base-artifact-preview.component';\n\n/**\n * Inline preview for video artifacts. Embeds the generic {@link MJMediaPlayerComponent}\n * (`mj-media-player`) in a compact, thumbnail-sized configuration inside the conversation message\n * card. Click events on the player are stopped from propagating so scrubbing / play-pause does\n * NOT open the full-size viewer (the card's other regions still open it).\n *\n * Compact gating: play/pause + scrubber + time (and fullscreen, which is handy for a tiny video\n * surface) are shown. Transcript, waveform, speed, skip and volume chrome are turned OFF so the\n * player reads cleanly at preview size and doesn't crowd the card.\n */\n@Component({\n standalone: false,\n selector: 'mj-video-artifact-preview',\n template: `\n @if (errorMessage) {\n <div class=\"video-preview video-preview--error\">\n <i class=\"fa-solid fa-film\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else if (isLoading) {\n <mj-loading text=\"Loading video...\" size=\"small\"></mj-loading>\n } @else if (videoUrl) {\n <mj-media-player\n class=\"video-preview__player\"\n [Tracks]=\"MediaTracks\"\n [ShowTranscript]=\"false\"\n [ShowTranscriptToggle]=\"false\"\n [ShowWaveform]=\"false\"\n [ShowSpeedControl]=\"false\"\n [ShowSkipControls]=\"false\"\n [ShowVolume]=\"false\"\n (click)=\"$event.stopPropagation()\"\n (Ended)=\"onMediaEnded()\"\n ></mj-media-player>\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n /* Compact thumbnail — cap the height so the video surface stays consistent with the\n image preview (140px) and neither dominates the conversation card. */\n .video-preview__player {\n display: block;\n width: 100%;\n max-width: 100%;\n max-height: 140px;\n border-radius: 6px;\n overflow: hidden;\n background: var(--mj-bg-surface-sunken);\n }\n\n .video-preview--error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n `,\n ],\n})\nexport class VideoArtifactPreviewComponent extends BaseArtifactPreviewComponent implements OnInit {\n private readonly cdr = inject(ChangeDetectorRef);\n\n /** Resolved URL — data URI (inline) or pre-auth URL (file). Funneled through {@link setVideoUrl}. */\n public videoUrl = '';\n\n\n /** Non-empty hides the player and shows a compact error line. */\n public errorMessage = '';\n\n /**\n * Single-track input for the generic {@link MJMediaPlayerComponent}. Rebuilt in lockstep with\n * {@link videoUrl} via {@link setVideoUrl}; `[]` until a URL exists so the player shows its empty state.\n */\n public MediaTracks: MediaTrack[] = [];\n\n /** Set the resolved URL and recompute the single-element {@link MediaTracks} array in lockstep. */\n private setVideoUrl(url: string): void {\n this.videoUrl = url;\n this.MediaTracks = url\n ? [\n {\n Id: this.artifactVersion?.ID ?? 'video',\n Kind: 'video',\n Url: url,\n MimeType: this.artifactVersion?.MimeType ?? undefined,\n },\n ]\n : [];\n }\n\n async ngOnInit(): Promise<void> {\n try {\n const url = await this.resolveContentUrl();\n if (url) {\n this.setVideoUrl(url);\n } else {\n this.errorMessage = 'No video content.';\n }\n } catch {\n this.errorMessage = 'Could not load video.';\n }\n this.cdr.markForCheck();\n }\n\n public onMediaError(): void {\n this.errorMessage = 'Video could not be played.';\n this.setVideoUrl('');\n this.cdr.markForCheck();\n }\n\n /** Fired when the generic player reaches the end of the track. No-op affordance hook. */\n public onMediaEnded(): void {\n // Playback finished — nothing to persist for an inline preview.\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-artifacts",
3
- "version": "6.1.0-edge.5",
3
+ "version": "6.1.0-edge.6",
4
4
  "description": "MemberJunction: Artifact viewer plugin system for rendering different artifact types (JSON, Code, Markdown, HTML, SVG, Components)",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -11,42 +11,42 @@
11
11
  "author": "",
12
12
  "license": "BUSL-1.1",
13
13
  "devDependencies": {
14
- "@angular/compiler": "21.1.3",
15
- "@angular/compiler-cli": "21.1.3",
16
- "@memberjunction/core": "6.1.0-edge.5",
17
- "@memberjunction/ng-test-utils": "6.1.0-edge.5",
14
+ "@angular/compiler": "21.2.22",
15
+ "@angular/compiler-cli": "21.2.22",
16
+ "@memberjunction/core": "6.1.0-edge.6",
17
+ "@memberjunction/ng-test-utils": "6.1.0-edge.6",
18
18
  "sass": "^1.97.3",
19
19
  "vitest": "^4.0.18"
20
20
  },
21
21
  "peerDependencies": {
22
- "@angular/common": "^21.1.3",
23
- "@angular/core": "^21.1.3",
24
- "@angular/platform-browser": "^21.1.3",
25
- "@angular/animations": "^21.1.3",
22
+ "@angular/animations": "^21.2.22",
23
+ "@angular/cdk": "^21.2.14",
24
+ "@angular/common": "^21.2.22",
25
+ "@angular/core": "^21.2.22",
26
+ "@angular/forms": "^21.2.22",
27
+ "@angular/platform-browser": "^21.2.22",
26
28
  "ag-grid-angular": "^35.0.1",
27
29
  "ag-grid-community": "^35.0.1"
28
30
  },
29
31
  "dependencies": {
30
- "@angular/cdk": "21.1.3",
31
- "@angular/forms": "21.1.3",
32
- "@memberjunction/core": "6.1.0-edge.5",
33
- "@memberjunction/core-entities": "6.1.0-edge.5",
34
- "@memberjunction/global": "6.1.0-edge.5",
35
- "@memberjunction/graphql-dataprovider": "6.1.0-edge.5",
36
- "@memberjunction/interactive-component-types": "6.1.0-edge.5",
37
- "@memberjunction/ng-base-forms": "6.1.0-edge.5",
38
- "@memberjunction/ng-base-types": "6.1.0-edge.5",
39
- "@memberjunction/ng-code-editor": "6.1.0-edge.5",
40
- "@memberjunction/ng-export-service": "6.1.0-edge.5",
41
- "@memberjunction/ng-markdown": "6.1.0-edge.5",
42
- "@memberjunction/ng-media-player": "6.1.0-edge.5",
43
- "@memberjunction/ng-notifications": "6.1.0-edge.5",
44
- "@memberjunction/ng-pagination": "6.1.0-edge.5",
45
- "@memberjunction/ng-query-viewer": "6.1.0-edge.5",
46
- "@memberjunction/ng-react": "6.1.0-edge.5",
47
- "@memberjunction/ng-shared-generic": "6.1.0-edge.5",
48
- "@memberjunction/ng-trees": "6.1.0-edge.5",
49
- "@memberjunction/ng-ui-components": "6.1.0-edge.5",
32
+ "@memberjunction/core": "6.1.0-edge.6",
33
+ "@memberjunction/core-entities": "6.1.0-edge.6",
34
+ "@memberjunction/global": "6.1.0-edge.6",
35
+ "@memberjunction/graphql-dataprovider": "6.1.0-edge.6",
36
+ "@memberjunction/interactive-component-types": "6.1.0-edge.6",
37
+ "@memberjunction/ng-base-forms": "6.1.0-edge.6",
38
+ "@memberjunction/ng-base-types": "6.1.0-edge.6",
39
+ "@memberjunction/ng-code-editor": "6.1.0-edge.6",
40
+ "@memberjunction/ng-export-service": "6.1.0-edge.6",
41
+ "@memberjunction/ng-markdown": "6.1.0-edge.6",
42
+ "@memberjunction/ng-media-player": "6.1.0-edge.6",
43
+ "@memberjunction/ng-notifications": "6.1.0-edge.6",
44
+ "@memberjunction/ng-pagination": "6.1.0-edge.6",
45
+ "@memberjunction/ng-query-viewer": "6.1.0-edge.6",
46
+ "@memberjunction/ng-react": "6.1.0-edge.6",
47
+ "@memberjunction/ng-shared-generic": "6.1.0-edge.6",
48
+ "@memberjunction/ng-trees": "6.1.0-edge.6",
49
+ "@memberjunction/ng-ui-components": "6.1.0-edge.6",
50
50
  "mammoth": "^1.8.0",
51
51
  "pdfjs-dist": "^4.10.38",
52
52
  "rxjs": "^7.8.2",