@memberjunction/ng-artifacts 5.28.0 → 5.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/__tests__/artifact-snapshots.test.d.ts +2 -0
- package/dist/lib/__tests__/artifact-snapshots.test.d.ts.map +1 -0
- package/dist/lib/__tests__/artifact-snapshots.test.js +155 -0
- package/dist/lib/__tests__/artifact-snapshots.test.js.map +1 -0
- package/dist/lib/components/artifact-viewer-panel.component.d.ts +16 -2
- package/dist/lib/components/artifact-viewer-panel.component.d.ts.map +1 -1
- package/dist/lib/components/artifact-viewer-panel.component.js +128 -98
- package/dist/lib/components/artifact-viewer-panel.component.js.map +1 -1
- package/dist/lib/components/base-artifact-viewer.component.d.ts +24 -0
- package/dist/lib/components/base-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/base-artifact-viewer.component.js +15 -1
- package/dist/lib/components/base-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/code-artifact-viewer.component.d.ts +2 -0
- package/dist/lib/components/plugins/code-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/code-artifact-viewer.component.js +5 -1
- package/dist/lib/components/plugins/code-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts +2 -1
- package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/component-artifact-viewer.component.js +29 -0
- package/dist/lib/components/plugins/component-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts +50 -43
- package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/data-artifact-viewer.component.js +336 -137
- package/dist/lib/components/plugins/data-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/docx-artifact-viewer.component.d.ts +2 -0
- package/dist/lib/components/plugins/docx-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/docx-artifact-viewer.component.js +21 -6
- package/dist/lib/components/plugins/docx-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/html-artifact-viewer.component.d.ts +2 -0
- package/dist/lib/components/plugins/html-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/html-artifact-viewer.component.js +11 -1
- package/dist/lib/components/plugins/html-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/json-artifact-viewer.component.d.ts +2 -0
- package/dist/lib/components/plugins/json-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/json-artifact-viewer.component.js +5 -1
- package/dist/lib/components/plugins/json-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/markdown-artifact-viewer.component.d.ts +2 -0
- package/dist/lib/components/plugins/markdown-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/markdown-artifact-viewer.component.js +5 -1
- package/dist/lib/components/plugins/markdown-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/pdf-artifact-viewer.component.d.ts +2 -0
- package/dist/lib/components/plugins/pdf-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/pdf-artifact-viewer.component.js +29 -10
- package/dist/lib/components/plugins/pdf-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/save-query-dialog.component.js +2 -2
- package/dist/lib/components/plugins/save-query-dialog.component.js.map +1 -1
- package/dist/lib/components/plugins/svg-artifact-viewer.component.d.ts +2 -0
- package/dist/lib/components/plugins/svg-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/svg-artifact-viewer.component.js +11 -1
- package/dist/lib/components/plugins/svg-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/xlsx-artifact-viewer.component.d.ts +2 -0
- package/dist/lib/components/plugins/xlsx-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js +39 -13
- package/dist/lib/components/plugins/xlsx-artifact-viewer.component.js.map +1 -1
- package/dist/lib/services/analyze-artifact.service.d.ts +77 -0
- package/dist/lib/services/analyze-artifact.service.d.ts.map +1 -0
- package/dist/lib/services/analyze-artifact.service.js +120 -0
- package/dist/lib/services/analyze-artifact.service.js.map +1 -0
- package/dist/lib/snapshot-helpers.d.ts +48 -0
- package/dist/lib/snapshot-helpers.d.ts.map +1 -0
- package/dist/lib/snapshot-helpers.js +79 -0
- package/dist/lib/snapshot-helpers.js.map +1 -0
- package/dist/public-api.d.ts +2 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +3 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +16 -16
|
@@ -401,7 +401,7 @@ export class SaveQueryPanelComponent {
|
|
|
401
401
|
i0.ɵɵconditional(ctx.IsSaving ? 32 : 33);
|
|
402
402
|
i0.ɵɵadvance(2);
|
|
403
403
|
i0.ɵɵproperty("disabled", ctx.IsSaving);
|
|
404
|
-
} }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.TreeDropdownComponent], styles: ["\n\n .sqp-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible[_ngcontent-%COMP%] {\n background: rgba(0, 0, 0, 0.3);\n pointer-events: auto;\n }\n\n \n\n\n\n .sqp-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--
|
|
404
|
+
} }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.TreeDropdownComponent], styles: ["\n\n .sqp-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible[_ngcontent-%COMP%] {\n background: var(--mj-bg-overlay, rgba(0, 0, 0, 0.3));\n pointer-events: auto;\n }\n\n \n\n\n\n .sqp-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--mj-bg-surface);\n box-shadow: -8px 0 32px rgba(0, 0, 0, 0.12);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n }\n .sqp-panel.sqp-visible[_ngcontent-%COMP%] {\n right: 0;\n }\n\n \n\n .sqp-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n .sqp-title-group[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .sqp-title-icon[_ngcontent-%COMP%] {\n font-size: 20px;\n color: var(--mj-brand-primary);\n }\n .sqp-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n .sqp-close-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n border-radius: 8px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 16px;\n flex-shrink: 0;\n }\n .sqp-close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n \n\n .sqp-body[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n }\n\n \n\n .sqp-field[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n }\n .sqp-label-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .sqp-label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 6px;\n font-weight: 600;\n font-size: 13px;\n color: var(--mj-text-primary);\n }\n .sqp-label-row[_ngcontent-%COMP%] .sqp-label[_ngcontent-%COMP%] {\n margin-bottom: 0;\n }\n .sqp-sublabel[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n .sqp-required[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n }\n .sqp-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-disabled);\n }\n .sqp-textarea[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n resize: vertical;\n font-family: inherit;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-textarea[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-textarea[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-disabled);\n }\n\n \n\n .sqp-add-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n background: none;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 12px;\n color: var(--mj-brand-primary);\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-add-btn[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n }\n\n \n\n .sqp-create-category[_ngcontent-%COMP%] {\n margin-top: 12px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n }\n .sqp-create-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n margin-bottom: 10px;\n }\n .sqp-create-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n }\n .sqp-create-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .sqp-create-parent[_ngcontent-%COMP%] {\n margin-top: 2px;\n }\n .sqp-create-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n .sqp-inline-error[_ngcontent-%COMP%] {\n margin-top: 8px;\n font-size: 12px;\n color: var(--mj-status-error-text);\n }\n\n \n\n .sqp-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n .sqp-btn-save[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-save[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n }\n .sqp-btn-cancel[_ngcontent-%COMP%] {\n background: none;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-cancel[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .sqp-btn-create[_ngcontent-%COMP%] {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n }\n .sqp-btn-create[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 85%, black);\n }\n .sqp-btn-cancel-create[_ngcontent-%COMP%] {\n background: none;\n color: var(--mj-text-secondary);\n }\n .sqp-btn-cancel-create[_ngcontent-%COMP%]:hover:not(:disabled) {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-sunken);\n }\n\n \n\n .sqp-footer[_ngcontent-%COMP%] {\n display: flex;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n \n\n .sqp-error[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 6px;\n color: var(--mj-status-error-text);\n font-size: 13px;\n }\n\n \n\n @media (max-width: 768px) {\n .sqp-panel[_ngcontent-%COMP%] {\n width: 100% !important;\n right: -100%;\n }\n .sqp-panel.sqp-visible[_ngcontent-%COMP%] {\n right: 0;\n }\n .sqp-header[_ngcontent-%COMP%] {\n padding: 16px 20px 12px;\n }\n .sqp-body[_ngcontent-%COMP%] {\n padding: 16px;\n }\n .sqp-footer[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n }"], changeDetection: 0 });
|
|
405
405
|
}
|
|
406
406
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SaveQueryPanelComponent, [{
|
|
407
407
|
type: Component,
|
|
@@ -545,7 +545,7 @@ export class SaveQueryPanelComponent {
|
|
|
545
545
|
</button>
|
|
546
546
|
</div>
|
|
547
547
|
</div>
|
|
548
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["\n /* Backdrop */\n .sqp-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible {\n background: rgba(0, 0, 0, 0.3);\n pointer-events: auto;\n }\n\n /* Panel \u2014 uses right offset instead of transform to avoid creating\n a new containing block that breaks position:fixed children\n (e.g., the tree-dropdown popup). */\n .sqp-panel {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--
|
|
548
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["\n /* Backdrop */\n .sqp-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible {\n background: var(--mj-bg-overlay, rgba(0, 0, 0, 0.3));\n pointer-events: auto;\n }\n\n /* Panel \u2014 uses right offset instead of transform to avoid creating\n a new containing block that breaks position:fixed children\n (e.g., the tree-dropdown popup). */\n .sqp-panel {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--mj-bg-surface);\n box-shadow: -8px 0 32px rgba(0, 0, 0, 0.12);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n\n /* Header */\n .sqp-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n .sqp-title-group {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .sqp-title-icon {\n font-size: 20px;\n color: var(--mj-brand-primary);\n }\n .sqp-title {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n .sqp-close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n border-radius: 8px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 16px;\n flex-shrink: 0;\n }\n .sqp-close-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n /* Body */\n .sqp-body {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n }\n\n /* Fields */\n .sqp-field {\n margin-bottom: 20px;\n }\n .sqp-label-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .sqp-label {\n display: block;\n margin-bottom: 6px;\n font-weight: 600;\n font-size: 13px;\n color: var(--mj-text-primary);\n }\n .sqp-label-row .sqp-label {\n margin-bottom: 0;\n }\n .sqp-sublabel {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n .sqp-required {\n color: var(--mj-status-error);\n }\n .sqp-input {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-input:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-input::placeholder {\n color: var(--mj-text-disabled);\n }\n .sqp-textarea {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n resize: vertical;\n font-family: inherit;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-textarea:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-textarea::placeholder {\n color: var(--mj-text-disabled);\n }\n\n /* Add category button */\n .sqp-add-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n background: none;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 12px;\n color: var(--mj-brand-primary);\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-add-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n }\n\n /* Inline create category */\n .sqp-create-category {\n margin-top: 12px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n }\n .sqp-create-header {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n margin-bottom: 10px;\n }\n .sqp-create-header i {\n color: var(--mj-brand-primary);\n }\n .sqp-create-fields {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .sqp-create-parent {\n margin-top: 2px;\n }\n .sqp-create-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n .sqp-inline-error {\n margin-top: 8px;\n font-size: 12px;\n color: var(--mj-status-error-text);\n }\n\n /* Buttons */\n .sqp-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n .sqp-btn-save {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-save:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n }\n .sqp-btn-cancel {\n background: none;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-cancel:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .sqp-btn-create {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n }\n .sqp-btn-create:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 85%, black);\n }\n .sqp-btn-cancel-create {\n background: none;\n color: var(--mj-text-secondary);\n }\n .sqp-btn-cancel-create:hover:not(:disabled) {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-sunken);\n }\n\n /* Footer */\n .sqp-footer {\n display: flex;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n /* Error */\n .sqp-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 6px;\n color: var(--mj-status-error-text);\n font-size: 13px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .sqp-panel {\n width: 100% !important;\n right: -100%;\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n .sqp-header {\n padding: 16px 20px 12px;\n }\n .sqp-body {\n padding: 16px;\n }\n .sqp-footer {\n padding: 12px 16px;\n }\n }\n "] }]
|
|
549
549
|
}], () => [{ type: i0.ChangeDetectorRef }], { QueryName: [{
|
|
550
550
|
type: Input
|
|
551
551
|
}], QueryDescription: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"save-query-dialog.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/save-query-dialog.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, HostListener } from '@angular/core';\nimport { Metadata, CompositeKey, KeyValuePair, BaseEntity } from '@memberjunction/core';\nimport { MJQueryEntity, MJQueryCategoryEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { TreeBranchConfig, TreeNode } from '@memberjunction/ng-trees';\nimport { TreeDropdownComponent } from '@memberjunction/ng-trees';\n\n/**\n * Result emitted when a query is successfully saved\n */\nexport interface SaveQueryResult {\n queryId: string;\n queryName: string;\n}\n\n/**\n * Slide-in panel for saving an ad-hoc SQL query as a reusable MJ Query record.\n * Pre-populates name from the artifact title and provides a tree dropdown\n * for selecting a query category, with inline category creation.\n *\n * Follows the CreateAgentSlideInComponent pattern for slide-in UX.\n */\n@Component({\n standalone: false,\n selector: 'mj-save-query-panel',\n template: `\n <!-- Backdrop -->\n <div class=\"sqp-backdrop\" [class.sqp-visible]=\"IsVisible\" (click)=\"OnCancel()\"></div>\n\n <!-- Slide panel -->\n <div class=\"sqp-panel\" [class.sqp-visible]=\"IsVisible\">\n <!-- Header -->\n <div class=\"sqp-header\">\n <div class=\"sqp-title-group\">\n <i class=\"fa-solid fa-save sqp-title-icon\"></i>\n <h2 class=\"sqp-title\">Save Query</h2>\n </div>\n <button class=\"sqp-close-btn\" (click)=\"OnCancel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"sqp-body\">\n <!-- Name -->\n <div class=\"sqp-field\">\n <label class=\"sqp-label\">\n Name <span class=\"sqp-required\">*</span>\n </label>\n <input\n type=\"text\"\n class=\"sqp-input\"\n [(ngModel)]=\"Name\"\n placeholder=\"Query name\"\n (keydown.enter)=\"OnSave()\">\n </div>\n\n <!-- Category -->\n <div class=\"sqp-field\">\n <div class=\"sqp-label-row\">\n <label class=\"sqp-label\">Category</label>\n @if (!IsCreatingCategory) {\n <button class=\"sqp-add-btn\" (click)=\"StartCreateCategory()\" title=\"Create new category\">\n <i class=\"fa-solid fa-plus\"></i> New\n </button>\n }\n </div>\n\n <mj-tree-dropdown\n #categoryTree\n [BranchConfig]=\"CategoryBranchConfig\"\n [SelectionMode]=\"'single'\"\n [SelectableTypes]=\"'branch'\"\n [Placeholder]=\"'Select a category (optional)'\"\n [Value]=\"SelectedCategoryKey\"\n (SelectionChange)=\"OnCategoryChanged($event)\">\n </mj-tree-dropdown>\n\n <!-- Inline create category form -->\n @if (IsCreatingCategory) {\n <div class=\"sqp-create-category\">\n <div class=\"sqp-create-header\">\n <i class=\"fa-solid fa-folder-plus\"></i>\n <span>New Category</span>\n </div>\n <div class=\"sqp-create-fields\">\n <input\n type=\"text\"\n class=\"sqp-input\"\n [(ngModel)]=\"NewCategoryName\"\n placeholder=\"Category name\"\n (keydown.enter)=\"CreateCategory()\"\n (keydown.escape)=\"CancelCreateCategory()\">\n <div class=\"sqp-create-parent\">\n <label class=\"sqp-sublabel\">Parent (optional)</label>\n <mj-tree-dropdown\n [BranchConfig]=\"CategoryBranchConfig\"\n [SelectionMode]=\"'single'\"\n [SelectableTypes]=\"'branch'\"\n [Placeholder]=\"'None (top level)'\"\n (SelectionChange)=\"OnNewCategoryParentChanged($event)\">\n </mj-tree-dropdown>\n </div>\n </div>\n <div class=\"sqp-create-actions\">\n <button class=\"sqp-btn sqp-btn-create\"\n (click)=\"CreateCategory()\"\n [disabled]=\"!NewCategoryName.trim() || IsCreatingCategorySaving\">\n @if (IsCreatingCategorySaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-check\"></i>\n }\n {{ IsCreatingCategorySaving ? 'Creating...' : 'Create' }}\n </button>\n <button class=\"sqp-btn sqp-btn-cancel-create\"\n (click)=\"CancelCreateCategory()\"\n [disabled]=\"IsCreatingCategorySaving\">\n Cancel\n </button>\n </div>\n @if (CreateCategoryError) {\n <div class=\"sqp-inline-error\">{{ CreateCategoryError }}</div>\n }\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"sqp-field\">\n <label class=\"sqp-label\">Description</label>\n <textarea\n class=\"sqp-textarea\"\n [(ngModel)]=\"Description\"\n placeholder=\"Optional description\"\n rows=\"3\">\n </textarea>\n </div>\n\n @if (ErrorMessage) {\n <div class=\"sqp-error\">\n <i class=\"fa-solid fa-exclamation-circle\"></i>\n {{ ErrorMessage }}\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"sqp-footer\">\n <button class=\"sqp-btn sqp-btn-save\"\n (click)=\"OnSave()\"\n [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Saving...\n } @else {\n <i class=\"fa-solid fa-save\"></i> Save Query\n }\n </button>\n <button class=\"sqp-btn sqp-btn-cancel\"\n (click)=\"OnCancel()\"\n [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n `,\n styles: [`\n /* Backdrop */\n .sqp-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible {\n background: rgba(0, 0, 0, 0.3);\n pointer-events: auto;\n }\n\n /* Panel — uses right offset instead of transform to avoid creating\n a new containing block that breaks position:fixed children\n (e.g., the tree-dropdown popup). */\n .sqp-panel {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--card-background, #ffffff);\n box-shadow: -8px 0 32px rgba(0, 0, 0, 0.12);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n\n /* Header */\n .sqp-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n flex-shrink: 0;\n }\n .sqp-title-group {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .sqp-title-icon {\n font-size: 20px;\n color: #5c6bc0;\n }\n .sqp-title {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n }\n .sqp-close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n border-radius: 8px;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 16px;\n flex-shrink: 0;\n }\n .sqp-close-btn:hover {\n background: var(--hover-background, #f3f4f6);\n color: var(--text-primary, #1f2937);\n }\n\n /* Body */\n .sqp-body {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n }\n\n /* Fields */\n .sqp-field {\n margin-bottom: 20px;\n }\n .sqp-label-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .sqp-label {\n display: block;\n margin-bottom: 6px;\n font-weight: 600;\n font-size: 13px;\n color: var(--text-primary, #374151);\n }\n .sqp-label-row .sqp-label {\n margin-bottom: 0;\n }\n .sqp-sublabel {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n font-size: 12px;\n color: #6b7280;\n }\n .sqp-required {\n color: #dc2626;\n }\n .sqp-input {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-size: 14px;\n color: #1f2937;\n background: #fff;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-input:focus {\n outline: none;\n border-color: #5c6bc0;\n box-shadow: 0 0 0 3px rgba(92, 107, 192, 0.15);\n }\n .sqp-input::placeholder {\n color: #9ca3af;\n }\n .sqp-textarea {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-size: 14px;\n color: #1f2937;\n background: #fff;\n resize: vertical;\n font-family: inherit;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-textarea:focus {\n outline: none;\n border-color: #5c6bc0;\n box-shadow: 0 0 0 3px rgba(92, 107, 192, 0.15);\n }\n .sqp-textarea::placeholder {\n color: #9ca3af;\n }\n\n /* Add category button */\n .sqp-add-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n background: none;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n font-size: 12px;\n color: #5c6bc0;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-add-btn:hover {\n background: #eef2ff;\n border-color: #5c6bc0;\n }\n\n /* Inline create category */\n .sqp-create-category {\n margin-top: 12px;\n padding: 14px;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n }\n .sqp-create-header {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: #475569;\n margin-bottom: 10px;\n }\n .sqp-create-header i {\n color: #5c6bc0;\n }\n .sqp-create-fields {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .sqp-create-parent {\n margin-top: 2px;\n }\n .sqp-create-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n .sqp-inline-error {\n margin-top: 8px;\n font-size: 12px;\n color: #dc2626;\n }\n\n /* Buttons */\n .sqp-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n .sqp-btn-save {\n background: #5c6bc0;\n color: #fff;\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-save:hover:not(:disabled) {\n background: #3f51b5;\n }\n .sqp-btn-cancel {\n background: none;\n border: 1px solid #d1d5db;\n color: #6b7280;\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-cancel:hover:not(:disabled) {\n background: #f3f4f6;\n color: #374151;\n }\n .sqp-btn-create {\n background: #16a34a;\n color: #fff;\n }\n .sqp-btn-create:hover:not(:disabled) {\n background: #15803d;\n }\n .sqp-btn-cancel-create {\n background: none;\n color: #6b7280;\n }\n .sqp-btn-cancel-create:hover:not(:disabled) {\n color: #374151;\n background: #f1f5f9;\n }\n\n /* Footer */\n .sqp-footer {\n display: flex;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--border-color, #e5e7eb);\n flex-shrink: 0;\n }\n\n /* Error */\n .sqp-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 6px;\n color: #dc2626;\n font-size: 13px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .sqp-panel {\n width: 100% !important;\n right: -100%;\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n .sqp-header {\n padding: 16px 20px 12px;\n }\n .sqp-body {\n padding: 16px;\n }\n .sqp-footer {\n padding: 12px 16px;\n }\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SaveQueryPanelComponent {\n @Input() QueryName = '';\n @Input() QueryDescription = '';\n @Input() SQL = '';\n @Input() Plan = '';\n\n @Output() Saved = new EventEmitter<SaveQueryResult>();\n @Output() Cancelled = new EventEmitter<void>();\n\n @ViewChild('categoryTree') categoryTree!: TreeDropdownComponent;\n\n // Form state\n public Name = '';\n public Description = '';\n public IsSaving = false;\n public ErrorMessage = '';\n public IsVisible = false;\n\n // Category selection\n private selectedCategoryId: string | null = null;\n public SelectedCategoryKey: CompositeKey | null = null;\n\n // Inline category creation\n public IsCreatingCategory = false;\n public IsCreatingCategorySaving = false;\n public NewCategoryName = '';\n public CreateCategoryError = '';\n private newCategoryParentId: string | null = null;\n\n /** Tree config for query category hierarchy */\n public CategoryBranchConfig: TreeBranchConfig = {\n EntityName: 'MJ: Query Categories',\n DisplayField: 'Name',\n IDField: 'ID',\n ParentIDField: 'ParentID',\n DefaultIcon: 'fa-solid fa-folder',\n OrderBy: 'Name ASC'\n };\n\n constructor(private cdr: ChangeDetectorRef) {}\n\n public get CanSave(): boolean {\n return !!this.Name?.trim();\n }\n\n ngOnInit(): void {\n // Sync input values\n this.Name = this.QueryName;\n this.Description = this.QueryDescription;\n\n // Trigger slide-in on next microtask (ensures DOM is ready)\n Promise.resolve().then(() => {\n this.IsVisible = true;\n this.cdr.markForCheck();\n });\n }\n\n @HostListener('document:keydown.escape')\n public OnEscapeKey(): void {\n if (this.IsCreatingCategory) {\n this.CancelCreateCategory();\n } else if (!this.IsSaving) {\n this.OnCancel();\n }\n }\n\n // =========================================================================\n // Category Selection\n // =========================================================================\n\n public OnCategoryChanged(selection: TreeNode | TreeNode[] | null): void {\n if (!selection) {\n this.selectedCategoryId = null;\n } else if (Array.isArray(selection)) {\n this.selectedCategoryId = selection.length > 0 ? selection[0].ID : null;\n } else {\n this.selectedCategoryId = selection.ID;\n }\n }\n\n // =========================================================================\n // Inline Category Creation\n // =========================================================================\n\n public StartCreateCategory(): void {\n this.IsCreatingCategory = true;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n }\n\n public CancelCreateCategory(): void {\n this.IsCreatingCategory = false;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n }\n\n public OnNewCategoryParentChanged(selection: TreeNode | TreeNode[] | null): void {\n if (!selection) {\n this.newCategoryParentId = null;\n } else if (Array.isArray(selection)) {\n this.newCategoryParentId = selection.length > 0 ? selection[0].ID : null;\n } else {\n this.newCategoryParentId = selection.ID;\n }\n }\n\n public async CreateCategory(): Promise<void> {\n const name = this.NewCategoryName.trim();\n if (!name || this.IsCreatingCategorySaving) return;\n\n this.IsCreatingCategorySaving = true;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n\n try {\n const md = new Metadata();\n const category = await md.GetEntityObject<MJQueryCategoryEntity>('MJ: Query Categories');\n\n category.Name = name;\n category.UserID = md.CurrentUser.ID;\n if (this.newCategoryParentId) {\n category.ParentID = this.newCategoryParentId;\n }\n\n const saved = await category.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(`Category \"${name}\" created`, 'success', 2500);\n\n // Refresh core metadata so Metadata.QueryCategories is up to date\n await md.Refresh();\n\n // Refresh tree to pick up the new category\n await this.categoryTree.Refresh();\n\n // Auto-select the newly created category.\n // Must use detectChanges() (not markForCheck) to immediately propagate\n // the new Value input to the tree dropdown, since this panel uses OnPush.\n const newKey = new CompositeKey([new KeyValuePair('ID', category.ID)]);\n this.SelectedCategoryKey = newKey;\n this.selectedCategoryId = category.ID;\n this.cdr.detectChanges();\n\n // Collapse the create form\n this.IsCreatingCategory = false;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n } else {\n const errorMsg = this.getEntityErrorMessage(category, 'create category');\n this.CreateCategoryError = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Failed to create category:', category.LatestResult);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'Failed to create category';\n this.CreateCategoryError = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Error creating category:', error);\n } finally {\n this.IsCreatingCategorySaving = false;\n this.cdr.markForCheck();\n }\n }\n\n // =========================================================================\n // Save / Cancel\n // =========================================================================\n\n public async OnSave(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.ErrorMessage = '';\n this.cdr.markForCheck();\n\n try {\n const md = new Metadata();\n const query = await md.GetEntityObject<MJQueryEntity>('MJ: Queries');\n\n query.Name = this.Name.trim();\n query.SQL = this.SQL;\n query.Status = 'Approved';\n\n if (this.Description?.trim()) {\n query.Description = this.Description.trim();\n }\n if (this.Plan?.trim()) {\n query.TechnicalDescription = this.Plan.trim();\n }\n if (this.selectedCategoryId) {\n query.CategoryID = this.selectedCategoryId;\n }\n\n const saved = await query.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(`Query \"${query.Name}\" saved`, 'success', 2500);\n\n // Animate out, then emit\n this.IsVisible = false;\n this.cdr.markForCheck();\n setTimeout(() => {\n this.Saved.emit({\n queryId: query.ID,\n queryName: query.Name\n });\n }, 300);\n } else {\n const errorMsg = this.getEntityErrorMessage(query, 'save query');\n this.ErrorMessage = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Failed to save query:', query.LatestResult);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'An unexpected error occurred';\n this.ErrorMessage = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Error saving query:', error);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public OnCancel(): void {\n if (this.IsSaving) return;\n this.IsVisible = false;\n this.cdr.markForCheck();\n setTimeout(() => this.Cancelled.emit(), 300);\n }\n\n // =========================================================================\n // Helpers\n // =========================================================================\n\n private getEntityErrorMessage(entity: BaseEntity, operation: string): string {\n const result = entity.LatestResult;\n if (result?.Message) {\n return result.Message;\n }\n return `Failed to ${operation}. Please try again.`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"save-query-dialog.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/save-query-dialog.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, HostListener } from '@angular/core';\nimport { Metadata, CompositeKey, KeyValuePair, BaseEntity } from '@memberjunction/core';\nimport { MJQueryEntity, MJQueryCategoryEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { TreeBranchConfig, TreeNode } from '@memberjunction/ng-trees';\nimport { TreeDropdownComponent } from '@memberjunction/ng-trees';\n\n/**\n * Result emitted when a query is successfully saved\n */\nexport interface SaveQueryResult {\n queryId: string;\n queryName: string;\n}\n\n/**\n * Slide-in panel for saving an ad-hoc SQL query as a reusable MJ Query record.\n * Pre-populates name from the artifact title and provides a tree dropdown\n * for selecting a query category, with inline category creation.\n *\n * Follows the CreateAgentSlideInComponent pattern for slide-in UX.\n */\n@Component({\n standalone: false,\n selector: 'mj-save-query-panel',\n template: `\n <!-- Backdrop -->\n <div class=\"sqp-backdrop\" [class.sqp-visible]=\"IsVisible\" (click)=\"OnCancel()\"></div>\n\n <!-- Slide panel -->\n <div class=\"sqp-panel\" [class.sqp-visible]=\"IsVisible\">\n <!-- Header -->\n <div class=\"sqp-header\">\n <div class=\"sqp-title-group\">\n <i class=\"fa-solid fa-save sqp-title-icon\"></i>\n <h2 class=\"sqp-title\">Save Query</h2>\n </div>\n <button class=\"sqp-close-btn\" (click)=\"OnCancel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"sqp-body\">\n <!-- Name -->\n <div class=\"sqp-field\">\n <label class=\"sqp-label\">\n Name <span class=\"sqp-required\">*</span>\n </label>\n <input\n type=\"text\"\n class=\"sqp-input\"\n [(ngModel)]=\"Name\"\n placeholder=\"Query name\"\n (keydown.enter)=\"OnSave()\">\n </div>\n\n <!-- Category -->\n <div class=\"sqp-field\">\n <div class=\"sqp-label-row\">\n <label class=\"sqp-label\">Category</label>\n @if (!IsCreatingCategory) {\n <button class=\"sqp-add-btn\" (click)=\"StartCreateCategory()\" title=\"Create new category\">\n <i class=\"fa-solid fa-plus\"></i> New\n </button>\n }\n </div>\n\n <mj-tree-dropdown\n #categoryTree\n [BranchConfig]=\"CategoryBranchConfig\"\n [SelectionMode]=\"'single'\"\n [SelectableTypes]=\"'branch'\"\n [Placeholder]=\"'Select a category (optional)'\"\n [Value]=\"SelectedCategoryKey\"\n (SelectionChange)=\"OnCategoryChanged($event)\">\n </mj-tree-dropdown>\n\n <!-- Inline create category form -->\n @if (IsCreatingCategory) {\n <div class=\"sqp-create-category\">\n <div class=\"sqp-create-header\">\n <i class=\"fa-solid fa-folder-plus\"></i>\n <span>New Category</span>\n </div>\n <div class=\"sqp-create-fields\">\n <input\n type=\"text\"\n class=\"sqp-input\"\n [(ngModel)]=\"NewCategoryName\"\n placeholder=\"Category name\"\n (keydown.enter)=\"CreateCategory()\"\n (keydown.escape)=\"CancelCreateCategory()\">\n <div class=\"sqp-create-parent\">\n <label class=\"sqp-sublabel\">Parent (optional)</label>\n <mj-tree-dropdown\n [BranchConfig]=\"CategoryBranchConfig\"\n [SelectionMode]=\"'single'\"\n [SelectableTypes]=\"'branch'\"\n [Placeholder]=\"'None (top level)'\"\n (SelectionChange)=\"OnNewCategoryParentChanged($event)\">\n </mj-tree-dropdown>\n </div>\n </div>\n <div class=\"sqp-create-actions\">\n <button class=\"sqp-btn sqp-btn-create\"\n (click)=\"CreateCategory()\"\n [disabled]=\"!NewCategoryName.trim() || IsCreatingCategorySaving\">\n @if (IsCreatingCategorySaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-check\"></i>\n }\n {{ IsCreatingCategorySaving ? 'Creating...' : 'Create' }}\n </button>\n <button class=\"sqp-btn sqp-btn-cancel-create\"\n (click)=\"CancelCreateCategory()\"\n [disabled]=\"IsCreatingCategorySaving\">\n Cancel\n </button>\n </div>\n @if (CreateCategoryError) {\n <div class=\"sqp-inline-error\">{{ CreateCategoryError }}</div>\n }\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"sqp-field\">\n <label class=\"sqp-label\">Description</label>\n <textarea\n class=\"sqp-textarea\"\n [(ngModel)]=\"Description\"\n placeholder=\"Optional description\"\n rows=\"3\">\n </textarea>\n </div>\n\n @if (ErrorMessage) {\n <div class=\"sqp-error\">\n <i class=\"fa-solid fa-exclamation-circle\"></i>\n {{ ErrorMessage }}\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"sqp-footer\">\n <button class=\"sqp-btn sqp-btn-save\"\n (click)=\"OnSave()\"\n [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Saving...\n } @else {\n <i class=\"fa-solid fa-save\"></i> Save Query\n }\n </button>\n <button class=\"sqp-btn sqp-btn-cancel\"\n (click)=\"OnCancel()\"\n [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n `,\n styles: [`\n /* Backdrop */\n .sqp-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible {\n background: var(--mj-bg-overlay, rgba(0, 0, 0, 0.3));\n pointer-events: auto;\n }\n\n /* Panel — uses right offset instead of transform to avoid creating\n a new containing block that breaks position:fixed children\n (e.g., the tree-dropdown popup). */\n .sqp-panel {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--mj-bg-surface);\n box-shadow: -8px 0 32px rgba(0, 0, 0, 0.12);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n\n /* Header */\n .sqp-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n .sqp-title-group {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .sqp-title-icon {\n font-size: 20px;\n color: var(--mj-brand-primary);\n }\n .sqp-title {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n .sqp-close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n border-radius: 8px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 16px;\n flex-shrink: 0;\n }\n .sqp-close-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n /* Body */\n .sqp-body {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n }\n\n /* Fields */\n .sqp-field {\n margin-bottom: 20px;\n }\n .sqp-label-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .sqp-label {\n display: block;\n margin-bottom: 6px;\n font-weight: 600;\n font-size: 13px;\n color: var(--mj-text-primary);\n }\n .sqp-label-row .sqp-label {\n margin-bottom: 0;\n }\n .sqp-sublabel {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n .sqp-required {\n color: var(--mj-status-error);\n }\n .sqp-input {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-input:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-input::placeholder {\n color: var(--mj-text-disabled);\n }\n .sqp-textarea {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 14px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n resize: vertical;\n font-family: inherit;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-textarea:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n .sqp-textarea::placeholder {\n color: var(--mj-text-disabled);\n }\n\n /* Add category button */\n .sqp-add-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n background: none;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 12px;\n color: var(--mj-brand-primary);\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-add-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n }\n\n /* Inline create category */\n .sqp-create-category {\n margin-top: 12px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n }\n .sqp-create-header {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n margin-bottom: 10px;\n }\n .sqp-create-header i {\n color: var(--mj-brand-primary);\n }\n .sqp-create-fields {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .sqp-create-parent {\n margin-top: 2px;\n }\n .sqp-create-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n .sqp-inline-error {\n margin-top: 8px;\n font-size: 12px;\n color: var(--mj-status-error-text);\n }\n\n /* Buttons */\n .sqp-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n .sqp-btn-save {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-save:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n }\n .sqp-btn-cancel {\n background: none;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-cancel:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .sqp-btn-create {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n }\n .sqp-btn-create:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 85%, black);\n }\n .sqp-btn-cancel-create {\n background: none;\n color: var(--mj-text-secondary);\n }\n .sqp-btn-cancel-create:hover:not(:disabled) {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-sunken);\n }\n\n /* Footer */\n .sqp-footer {\n display: flex;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n /* Error */\n .sqp-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 6px;\n color: var(--mj-status-error-text);\n font-size: 13px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .sqp-panel {\n width: 100% !important;\n right: -100%;\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n .sqp-header {\n padding: 16px 20px 12px;\n }\n .sqp-body {\n padding: 16px;\n }\n .sqp-footer {\n padding: 12px 16px;\n }\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SaveQueryPanelComponent {\n @Input() QueryName = '';\n @Input() QueryDescription = '';\n @Input() SQL = '';\n @Input() Plan = '';\n\n @Output() Saved = new EventEmitter<SaveQueryResult>();\n @Output() Cancelled = new EventEmitter<void>();\n\n @ViewChild('categoryTree') categoryTree!: TreeDropdownComponent;\n\n // Form state\n public Name = '';\n public Description = '';\n public IsSaving = false;\n public ErrorMessage = '';\n public IsVisible = false;\n\n // Category selection\n private selectedCategoryId: string | null = null;\n public SelectedCategoryKey: CompositeKey | null = null;\n\n // Inline category creation\n public IsCreatingCategory = false;\n public IsCreatingCategorySaving = false;\n public NewCategoryName = '';\n public CreateCategoryError = '';\n private newCategoryParentId: string | null = null;\n\n /** Tree config for query category hierarchy */\n public CategoryBranchConfig: TreeBranchConfig = {\n EntityName: 'MJ: Query Categories',\n DisplayField: 'Name',\n IDField: 'ID',\n ParentIDField: 'ParentID',\n DefaultIcon: 'fa-solid fa-folder',\n OrderBy: 'Name ASC'\n };\n\n constructor(private cdr: ChangeDetectorRef) {}\n\n public get CanSave(): boolean {\n return !!this.Name?.trim();\n }\n\n ngOnInit(): void {\n // Sync input values\n this.Name = this.QueryName;\n this.Description = this.QueryDescription;\n\n // Trigger slide-in on next microtask (ensures DOM is ready)\n Promise.resolve().then(() => {\n this.IsVisible = true;\n this.cdr.markForCheck();\n });\n }\n\n @HostListener('document:keydown.escape')\n public OnEscapeKey(): void {\n if (this.IsCreatingCategory) {\n this.CancelCreateCategory();\n } else if (!this.IsSaving) {\n this.OnCancel();\n }\n }\n\n // =========================================================================\n // Category Selection\n // =========================================================================\n\n public OnCategoryChanged(selection: TreeNode | TreeNode[] | null): void {\n if (!selection) {\n this.selectedCategoryId = null;\n } else if (Array.isArray(selection)) {\n this.selectedCategoryId = selection.length > 0 ? selection[0].ID : null;\n } else {\n this.selectedCategoryId = selection.ID;\n }\n }\n\n // =========================================================================\n // Inline Category Creation\n // =========================================================================\n\n public StartCreateCategory(): void {\n this.IsCreatingCategory = true;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n }\n\n public CancelCreateCategory(): void {\n this.IsCreatingCategory = false;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n }\n\n public OnNewCategoryParentChanged(selection: TreeNode | TreeNode[] | null): void {\n if (!selection) {\n this.newCategoryParentId = null;\n } else if (Array.isArray(selection)) {\n this.newCategoryParentId = selection.length > 0 ? selection[0].ID : null;\n } else {\n this.newCategoryParentId = selection.ID;\n }\n }\n\n public async CreateCategory(): Promise<void> {\n const name = this.NewCategoryName.trim();\n if (!name || this.IsCreatingCategorySaving) return;\n\n this.IsCreatingCategorySaving = true;\n this.CreateCategoryError = '';\n this.cdr.markForCheck();\n\n try {\n const md = new Metadata();\n const category = await md.GetEntityObject<MJQueryCategoryEntity>('MJ: Query Categories');\n\n category.Name = name;\n category.UserID = md.CurrentUser.ID;\n if (this.newCategoryParentId) {\n category.ParentID = this.newCategoryParentId;\n }\n\n const saved = await category.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(`Category \"${name}\" created`, 'success', 2500);\n\n // Refresh core metadata so Metadata.QueryCategories is up to date\n await md.Refresh();\n\n // Refresh tree to pick up the new category\n await this.categoryTree.Refresh();\n\n // Auto-select the newly created category.\n // Must use detectChanges() (not markForCheck) to immediately propagate\n // the new Value input to the tree dropdown, since this panel uses OnPush.\n const newKey = new CompositeKey([new KeyValuePair('ID', category.ID)]);\n this.SelectedCategoryKey = newKey;\n this.selectedCategoryId = category.ID;\n this.cdr.detectChanges();\n\n // Collapse the create form\n this.IsCreatingCategory = false;\n this.NewCategoryName = '';\n this.newCategoryParentId = null;\n } else {\n const errorMsg = this.getEntityErrorMessage(category, 'create category');\n this.CreateCategoryError = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Failed to create category:', category.LatestResult);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'Failed to create category';\n this.CreateCategoryError = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Error creating category:', error);\n } finally {\n this.IsCreatingCategorySaving = false;\n this.cdr.markForCheck();\n }\n }\n\n // =========================================================================\n // Save / Cancel\n // =========================================================================\n\n public async OnSave(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.ErrorMessage = '';\n this.cdr.markForCheck();\n\n try {\n const md = new Metadata();\n const query = await md.GetEntityObject<MJQueryEntity>('MJ: Queries');\n\n query.Name = this.Name.trim();\n query.SQL = this.SQL;\n query.Status = 'Approved';\n\n if (this.Description?.trim()) {\n query.Description = this.Description.trim();\n }\n if (this.Plan?.trim()) {\n query.TechnicalDescription = this.Plan.trim();\n }\n if (this.selectedCategoryId) {\n query.CategoryID = this.selectedCategoryId;\n }\n\n const saved = await query.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(`Query \"${query.Name}\" saved`, 'success', 2500);\n\n // Animate out, then emit\n this.IsVisible = false;\n this.cdr.markForCheck();\n setTimeout(() => {\n this.Saved.emit({\n queryId: query.ID,\n queryName: query.Name\n });\n }, 300);\n } else {\n const errorMsg = this.getEntityErrorMessage(query, 'save query');\n this.ErrorMessage = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Failed to save query:', query.LatestResult);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'An unexpected error occurred';\n this.ErrorMessage = errorMsg;\n MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);\n console.error('Error saving query:', error);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public OnCancel(): void {\n if (this.IsSaving) return;\n this.IsVisible = false;\n this.cdr.markForCheck();\n setTimeout(() => this.Cancelled.emit(), 300);\n }\n\n // =========================================================================\n // Helpers\n // =========================================================================\n\n private getEntityErrorMessage(entity: BaseEntity, operation: string): string {\n const result = entity.LatestResult;\n if (result?.Message) {\n return result.Message;\n }\n return `Failed to ${operation}. Please try again.`;\n }\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
|
2
|
+
import { DataSnapshot } from '@memberjunction/core';
|
|
2
3
|
import { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';
|
|
3
4
|
import * as i0 from "@angular/core";
|
|
4
5
|
/**
|
|
@@ -15,6 +16,7 @@ export declare class SvgArtifactViewerComponent extends BaseArtifactViewerPlugin
|
|
|
15
16
|
get hasDisplayContent(): boolean;
|
|
16
17
|
constructor(sanitizer: DomSanitizer);
|
|
17
18
|
ngOnInit(): void;
|
|
19
|
+
GetCurrentStateSnapshot(): DataSnapshot | null;
|
|
18
20
|
onCopy(): void;
|
|
19
21
|
static ɵfac: i0.ɵɵFactoryDeclaration<SvgArtifactViewerComponent, never>;
|
|
20
22
|
static ɵcmp: i0.ɵɵComponentDeclaration<SvgArtifactViewerComponent, "mj-svg-artifact-viewer", never, {}, {}, never, never, false, never>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg-artifact-viewer.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/svg-artifact-viewer.component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"svg-artifact-viewer.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/svg-artifact-viewer.component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;;AAEtF;;GAEG;AACH,qBAsGa,0BAA2B,SAAQ,iCAAiC;IAYnE,OAAO,CAAC,SAAS;IAXtB,UAAU,SAAM;IAChB,cAAc,EAAE,QAAQ,CAAM;IAC9B,QAAQ,EAAE,SAAS,GAAG,QAAQ,CAAa;IAElD;;OAEG;IACH,IAAoB,iBAAiB,IAAI,OAAO,CAE/C;gBAEmB,SAAS,EAAE,YAAY;IAI3C,QAAQ,IAAI,IAAI;IAMA,uBAAuB,IAAI,YAAY,GAAG,IAAI;IAS9D,MAAM,IAAI,IAAI;yCA/BH,0BAA0B;2CAA1B,0BAA0B;CAwCtC"}
|
|
@@ -5,6 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { Component } from '@angular/core';
|
|
8
|
+
import { DataSnapshot } from '@memberjunction/core';
|
|
8
9
|
import { RegisterClass } from '@memberjunction/global';
|
|
9
10
|
import { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';
|
|
10
11
|
import * as i0 from "@angular/core";
|
|
@@ -54,6 +55,15 @@ let SvgArtifactViewerComponent = class SvgArtifactViewerComponent extends BaseAr
|
|
|
54
55
|
// For SVG, we use sanitize with SecurityContext.HTML (1) to allow safe rendering
|
|
55
56
|
this.safeSvgContent = this.sanitizer.sanitize(1, this.svgContent) || '';
|
|
56
57
|
}
|
|
58
|
+
GetCurrentStateSnapshot() {
|
|
59
|
+
const content = this.getRawContent();
|
|
60
|
+
if (!content)
|
|
61
|
+
return null;
|
|
62
|
+
const snap = new DataSnapshot();
|
|
63
|
+
snap.title = this.getDisplayTitle() ?? undefined;
|
|
64
|
+
snap.custom = { content };
|
|
65
|
+
return snap;
|
|
66
|
+
}
|
|
57
67
|
onCopy() {
|
|
58
68
|
if (this.svgContent) {
|
|
59
69
|
navigator.clipboard.writeText(this.svgContent).then(() => {
|
|
@@ -131,5 +141,5 @@ export { SvgArtifactViewerComponent };
|
|
|
131
141
|
</div>
|
|
132
142
|
`, styles: ["\n .svg-artifact-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n\n .svg-toolbar {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: 8px;\n padding: 8px;\n background: #f5f5f5;\n border-bottom: 1px solid #ddd;\n }\n\n .btn-icon {\n padding: 6px 12px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .btn-icon:hover {\n background: #e9ecef;\n border-color: #999;\n }\n\n .btn-icon.active {\n background: #007acc;\n color: white;\n border-color: #007acc;\n }\n\n .svg-content-container {\n flex: 1;\n overflow: auto;\n }\n\n .svg-preview {\n padding: 20px;\n background: white;\n height: 100%;\n overflow: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .svg-wrapper {\n max-width: 100%;\n max-height: 100%;\n }\n\n .svg-wrapper ::ng-deep svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n }\n "] }]
|
|
133
143
|
}], () => [{ type: i1.DomSanitizer }], null); })();
|
|
134
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SvgArtifactViewerComponent, { className: "SvgArtifactViewerComponent", filePath: "src/lib/components/plugins/svg-artifact-viewer.component.ts", lineNumber:
|
|
144
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SvgArtifactViewerComponent, { className: "SvgArtifactViewerComponent", filePath: "src/lib/components/plugins/svg-artifact-viewer.component.ts", lineNumber: 112 }); })();
|
|
135
145
|
//# sourceMappingURL=svg-artifact-viewer.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg-artifact-viewer.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/svg-artifact-viewer.component.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;;;;;;;IAyB5E,8BAAyB;IACvB,0BAA4D;IAC9D,iBAAM;;;IADqB,cAA4B;IAA5B,oEAA4B;;;;IAGvD,0CAIqC;IAHnC,gUAAwB;IAI1B,iBAAiB;;;IAJf,iDAAwB;IAExB,AADA,gCAAkB,6BACG;;AA9BjC;;GAEG;AAuGI,IAAM,0BAA0B,GAAhC,MAAM,0BAA2B,SAAQ,iCAAiC;IAY3D;IAXb,UAAU,GAAG,EAAE,CAAC;IAChB,cAAc,GAAa,EAAE,CAAC;IAC9B,QAAQ,GAAyB,SAAS,CAAC;IAElD;;OAEG;IACH,IAAoB,iBAAiB;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,YAAoB,SAAuB;QACzC,KAAK,EAAE,CAAC;QADU,cAAS,GAAT,SAAS,CAAc;IAE3C,CAAC;IAED,QAAQ;QACN,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACpC,iFAAiF;QACjF,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAC1E,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;gBACvD,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;gBACb,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;YACrD,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;
|
|
1
|
+
{"version":3,"file":"svg-artifact-viewer.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/svg-artifact-viewer.component.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;;;;;;;IAyB5E,8BAAyB;IACvB,0BAA4D;IAC9D,iBAAM;;;IADqB,cAA4B;IAA5B,oEAA4B;;;;IAGvD,0CAIqC;IAHnC,gUAAwB;IAI1B,iBAAiB;;;IAJf,iDAAwB;IAExB,AADA,gCAAkB,6BACG;;AA9BjC;;GAEG;AAuGI,IAAM,0BAA0B,GAAhC,MAAM,0BAA2B,SAAQ,iCAAiC;IAY3D;IAXb,UAAU,GAAG,EAAE,CAAC;IAChB,cAAc,GAAa,EAAE,CAAC;IAC9B,QAAQ,GAAyB,SAAS,CAAC;IAElD;;OAEG;IACH,IAAoB,iBAAiB;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,YAAoB,SAAuB;QACzC,KAAK,EAAE,CAAC;QADU,cAAS,GAAT,SAAS,CAAc;IAE3C,CAAC;IAED,QAAQ;QACN,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACpC,iFAAiF;QACjF,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAC1E,CAAC;IAEe,uBAAuB;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACrC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,IAAI,SAAS,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;gBACvD,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;gBACb,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;YACrD,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;oHAvCU,0BAA0B;6DAA1B,0BAA0B;YAhG/B,AADF,AADF,8BAAsD,aAC3B,gBAEgC;YAA/B,sHAAoB,SAAS,IAAC;YACpD,uBAA0B;YAAC,yBAC7B;YAAA,iBAAS;YACT,iCACqD;YAA9B,sHAAoB,QAAQ,IAAC;YAClD,uBAA2B;YAAC,wBAC9B;YAAA,iBAAS;YACT,iCAA6D;YAAnB,uGAAS,YAAQ,IAAC;YAC1D,uBAA2B;YAAC,uBAC9B;YACF,AADE,iBAAS,EACL;YACN,+BAAmC;YAK/B,AAJF,8FAA8B,oFAIrB;YASb,AADE,iBAAM,EACF;;YA5B2B,sCAAoB;YAExB,eAAuC;YAAvC,oDAAuC;YAIvC,eAAsC;YAAtC,mDAAsC;YAS/D,eAWC;YAXD,sDAWC;;;AAwEI,0BAA0B;IADtC,aAAa,CAAC,iCAAiC,EAAE,yBAAyB,CAAC;GAC/D,0BAA0B,CAwCtC;;iFAxCY,0BAA0B;cAtGtC,SAAS;6BACI,KAAK,YACP,wBAAwB,YACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BT;;kFAqEU,0BAA0B","sourcesContent":["import { Component } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { DataSnapshot } from '@memberjunction/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';\n\n/**\n * Viewer component for SVG artifacts\n */\n@Component({\n standalone: false,\n selector: 'mj-svg-artifact-viewer',\n template: `\n <div class=\"svg-artifact-viewer\" [ngClass]=\"cssClass\">\n <div class=\"svg-toolbar\">\n <button class=\"btn-icon\" [class.active]=\"viewMode === 'preview'\"\n title=\"Preview\" (click)=\"viewMode = 'preview'\">\n <i class=\"fas fa-eye\"></i> Preview\n </button>\n <button class=\"btn-icon\" [class.active]=\"viewMode === 'source'\"\n title=\"Source\" (click)=\"viewMode = 'source'\">\n <i class=\"fas fa-code\"></i> Source\n </button>\n <button class=\"btn-icon\" title=\"Copy SVG\" (click)=\"onCopy()\">\n <i class=\"fas fa-copy\"></i> Copy\n </button>\n </div>\n <div class=\"svg-content-container\">\n @if (viewMode === 'preview') {\n <div class=\"svg-preview\">\n <div class=\"svg-wrapper\" [innerHTML]=\"safeSvgContent\"></div>\n </div>\n } @else {\n <mj-code-editor\n [(ngModel)]=\"svgContent\"\n [language]=\"'xml'\"\n [readonly]=\"readonly\"\n style=\"width: 100%; height: 100%;\">\n </mj-code-editor>\n }\n </div>\n </div>\n `,\n styles: [`\n .svg-artifact-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n\n .svg-toolbar {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: 8px;\n padding: 8px;\n background: #f5f5f5;\n border-bottom: 1px solid #ddd;\n }\n\n .btn-icon {\n padding: 6px 12px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .btn-icon:hover {\n background: #e9ecef;\n border-color: #999;\n }\n\n .btn-icon.active {\n background: #007acc;\n color: white;\n border-color: #007acc;\n }\n\n .svg-content-container {\n flex: 1;\n overflow: auto;\n }\n\n .svg-preview {\n padding: 20px;\n background: white;\n height: 100%;\n overflow: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .svg-wrapper {\n max-width: 100%;\n max-height: 100%;\n }\n\n .svg-wrapper ::ng-deep svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n }\n `]\n})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'SvgArtifactViewerPlugin')\nexport class SvgArtifactViewerComponent extends BaseArtifactViewerPluginComponent {\n public svgContent = '';\n public safeSvgContent: SafeHtml = '';\n public viewMode: 'preview' | 'source' = 'preview';\n\n /**\n * SVG artifacts always have content to display\n */\n public override get hasDisplayContent(): boolean {\n return true;\n }\n\n constructor(private sanitizer: DomSanitizer) {\n super();\n }\n\n ngOnInit(): void {\n this.svgContent = this.getContent();\n // For SVG, we use sanitize with SecurityContext.HTML (1) to allow safe rendering\n this.safeSvgContent = this.sanitizer.sanitize(1, this.svgContent) || '';\n }\n\n public override GetCurrentStateSnapshot(): DataSnapshot | null {\n const content = this.getRawContent();\n if (!content) return null;\n const snap = new DataSnapshot();\n snap.title = this.getDisplayTitle() ?? undefined;\n snap.custom = { content };\n return snap;\n }\n\n onCopy(): void {\n if (this.svgContent) {\n navigator.clipboard.writeText(this.svgContent).then(() => {\n console.log('✅ Copied SVG to clipboard');\n }).catch(err => {\n console.error('Failed to copy to clipboard:', err);\n });\n }\n }\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OnInit, ChangeDetectorRef } from '@angular/core';
|
|
2
|
+
import { DataSnapshot } from '@memberjunction/core';
|
|
2
3
|
import { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';
|
|
3
4
|
import { ArtifactFileService } from '../../services/artifact-file.service';
|
|
4
5
|
import { ColDef, GridReadyEvent } from 'ag-grid-community';
|
|
@@ -37,6 +38,7 @@ export declare class XlsxArtifactViewerComponent extends BaseArtifactViewerPlugi
|
|
|
37
38
|
private parseWorkbook;
|
|
38
39
|
private fetchAsArrayBuffer;
|
|
39
40
|
private showError;
|
|
41
|
+
GetCurrentStateSnapshot(): DataSnapshot | null;
|
|
40
42
|
static ɵfac: i0.ɵɵFactoryDeclaration<XlsxArtifactViewerComponent, never>;
|
|
41
43
|
static ɵcmp: i0.ɵɵComponentDeclaration<XlsxArtifactViewerComponent, "mj-xlsx-artifact-viewer", never, {}, {}, never, never, false, never>;
|
|
42
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xlsx-artifact-viewer.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/xlsx-artifact-viewer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,cAAc,EAAW,MAAM,mBAAmB,CAAC;;AAEpE,mDAAmD;AACnD,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC;IAC5D,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;GAKG;AACH,
|
|
1
|
+
{"version":3,"file":"xlsx-artifact-viewer.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/xlsx-artifact-viewer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,YAAY,EAAiC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,cAAc,EAAW,MAAM,mBAAmB,CAAC;;AAEpE,mDAAmD;AACnD,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC;IAC5D,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;GAKG;AACH,qBA6Ia,2BAA4B,SAAQ,iCAAkC,YAAW,MAAM;IAYhG,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,GAAG;IAZN,SAAS,UAAQ;IACjB,aAAa,UAAS;IACtB,YAAY,SAAM;IAClB,MAAM,EAAE,SAAS,EAAE,CAAM;IACzB,gBAAgB,SAAK;IACrB,aAAa,EAAE,MAAM,CAAmE;IAE/F,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,WAAW,CAAM;gBAGf,WAAW,EAAE,mBAAmB,EAChC,GAAG,EAAE,iBAAiB;IAKhC,IAAoB,iBAAiB,IAAI,OAAO,CAE/C;IAED,IAAW,WAAW,IAAI,SAAS,GAAG,IAAI,CAEzC;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAKxC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAO1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAgB1B,YAAY;IAsC1B,OAAO,CAAC,aAAa;YAmBP,kBAAkB;IAQhC,OAAO,CAAC,SAAS;IAMD,uBAAuB,IAAI,YAAY,GAAG,IAAI;yCAjInD,2BAA2B;2CAA3B,2BAA2B;CA0JvC"}
|
|
@@ -6,6 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { Component } from '@angular/core';
|
|
8
8
|
import { RegisterClass } from '@memberjunction/global';
|
|
9
|
+
import { DataSnapshot, DataTable, MJColumnDescriptor } from '@memberjunction/core';
|
|
9
10
|
import { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';
|
|
10
11
|
import * as i0 from "@angular/core";
|
|
11
12
|
import * as i1 from "../../services/artifact-file.service";
|
|
@@ -88,7 +89,9 @@ let XlsxArtifactViewerComponent = class XlsxArtifactViewerComponent extends Base
|
|
|
88
89
|
this.fileService = fileService;
|
|
89
90
|
this.cdr = cdr;
|
|
90
91
|
}
|
|
91
|
-
get hasDisplayContent() {
|
|
92
|
+
get hasDisplayContent() {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
92
95
|
get activeSheet() {
|
|
93
96
|
return this.sheets[this.activeSheetIndex] ?? null;
|
|
94
97
|
}
|
|
@@ -143,7 +146,7 @@ let XlsxArtifactViewerComponent = class XlsxArtifactViewerComponent extends Base
|
|
|
143
146
|
// Create an object URL for download support
|
|
144
147
|
this.downloadUrl = this.fileService.dataUrlToObjectUrl(content, this.artifactVersion.MimeType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
145
148
|
}
|
|
146
|
-
const XLSX = await import('xlsx');
|
|
149
|
+
const XLSX = (await import('xlsx'));
|
|
147
150
|
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
|
148
151
|
this.sheets = this.parseWorkbook(workbook, XLSX);
|
|
149
152
|
this.isLoading = false;
|
|
@@ -154,14 +157,14 @@ let XlsxArtifactViewerComponent = class XlsxArtifactViewerComponent extends Base
|
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
parseWorkbook(workbook, XLSX) {
|
|
157
|
-
return workbook.SheetNames.map(name => {
|
|
160
|
+
return workbook.SheetNames.map((name) => {
|
|
158
161
|
const sheet = workbook.Sheets[name];
|
|
159
162
|
const rows = XLSX.utils.sheet_to_json(sheet, {
|
|
160
163
|
defval: null,
|
|
161
164
|
raw: false, // Format dates/numbers as strings so AG Grid can display them
|
|
162
165
|
});
|
|
163
166
|
const headers = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
164
|
-
const columnDefs = headers.map(h => ({
|
|
167
|
+
const columnDefs = headers.map((h) => ({
|
|
165
168
|
field: h,
|
|
166
169
|
headerName: h,
|
|
167
170
|
tooltipField: h,
|
|
@@ -181,6 +184,30 @@ let XlsxArtifactViewerComponent = class XlsxArtifactViewerComponent extends Base
|
|
|
181
184
|
this.errorMessage = message;
|
|
182
185
|
this.cdr.markForCheck();
|
|
183
186
|
}
|
|
187
|
+
GetCurrentStateSnapshot() {
|
|
188
|
+
if (this.sheets.length === 0)
|
|
189
|
+
return null;
|
|
190
|
+
// Convert parsed sheets into DataTables for structured snapshot
|
|
191
|
+
const tables = this.sheets.map((sheet) => {
|
|
192
|
+
const table = new DataTable();
|
|
193
|
+
table.name = sheet.name;
|
|
194
|
+
table.source = 'static';
|
|
195
|
+
table.columns = sheet.columnDefs
|
|
196
|
+
.filter((col) => col.field)
|
|
197
|
+
.map((col) => {
|
|
198
|
+
const desc = new MJColumnDescriptor(col.field);
|
|
199
|
+
desc.displayName = col.headerName ?? col.field;
|
|
200
|
+
desc.sqlBaseType = 'nvarchar';
|
|
201
|
+
return desc;
|
|
202
|
+
});
|
|
203
|
+
table.rows = sheet.rowData;
|
|
204
|
+
table.metadata = { rowCount: sheet.rowData.length };
|
|
205
|
+
return table;
|
|
206
|
+
});
|
|
207
|
+
const snap = DataSnapshot.FromTables(tables, this.getDisplayTitle() ?? undefined);
|
|
208
|
+
snap.activeTab = this.sheets[this.activeSheetIndex]?.name;
|
|
209
|
+
return snap;
|
|
210
|
+
}
|
|
184
211
|
static ɵfac = function XlsxArtifactViewerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || XlsxArtifactViewerComponent)(i0.ɵɵdirectiveInject(i1.ArtifactFileService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
185
212
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: XlsxArtifactViewerComponent, selectors: [["mj-xlsx-artifact-viewer"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 4, consts: [[1, "xlsx-viewer"], [3, "download", "fileName", "isDownloading", "showPrint"], [1, "xlsx-viewer__state"], [1, "xlsx-viewer__state", "xlsx-viewer__state--error"], [1, "fas", "fa-spinner", "fa-spin"], [1, "fas", "fa-exclamation-triangle"], [1, "xlsx-viewer__tabs"], [1, "xlsx-viewer__grid"], [1, "ag-theme-quartz", 3, "gridReady", "rowData", "columnDefs", "defaultColDef", "animateRows", "suppressMovableColumns", "enableCellTextSelection"], [1, "xlsx-viewer__tab", 3, "xlsx-viewer__tab--active"], [1, "xlsx-viewer__tab", 3, "click"], [1, "fas", "fa-table"]], template: function XlsxArtifactViewerComponent_Template(rf, ctx) { if (rf & 1) {
|
|
186
213
|
i0.ɵɵelementStart(0, "div", 0)(1, "mj-file-artifact-toolbar", 1);
|
|
@@ -193,7 +220,7 @@ let XlsxArtifactViewerComponent = class XlsxArtifactViewerComponent extends Base
|
|
|
193
220
|
i0.ɵɵproperty("fileName", (ctx.artifactVersion == null ? null : ctx.artifactVersion.FileName) || "workbook.xlsx")("isDownloading", ctx.isDownloading)("showPrint", false);
|
|
194
221
|
i0.ɵɵadvance();
|
|
195
222
|
i0.ɵɵconditional(ctx.isLoading ? 2 : ctx.errorMessage ? 3 : 4);
|
|
196
|
-
} }, dependencies: [i2.AgGridAngular, i3.FileArtifactToolbarComponent], styles: [".xlsx-viewer[_ngcontent-%COMP%] {\n
|
|
223
|
+
} }, dependencies: [i2.AgGridAngular, i3.FileArtifactToolbarComponent], styles: [".xlsx-viewer[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n flex: 1;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .xlsx-viewer__state--error[_ngcontent-%COMP%] {\n color: var(--mj-status-error-text);\n }\n\n .xlsx-viewer__tabs[_ngcontent-%COMP%] {\n display: flex;\n gap: 2px;\n padding: 4px 8px 0;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n overflow-x: auto;\n flex-shrink: 0;\n }\n\n .xlsx-viewer__tab[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n color: var(--mj-text-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n transition:\n background 0.1s,\n color 0.1s;\n }\n\n .xlsx-viewer__tab[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .xlsx-viewer__tab--active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border-color: var(--mj-border-default);\n font-weight: 600;\n position: relative;\n }\n\n .xlsx-viewer__tab--active[_ngcontent-%COMP%]::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: 1px;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__grid[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n .xlsx-viewer__grid[_ngcontent-%COMP%] ag-grid-angular[_ngcontent-%COMP%] {\n height: 100%;\n width: 100%;\n display: block;\n }"] });
|
|
197
224
|
};
|
|
198
225
|
XlsxArtifactViewerComponent = __decorate([
|
|
199
226
|
RegisterClass(BaseArtifactViewerPluginComponent, 'XlsxArtifactViewerPlugin')
|
|
@@ -207,7 +234,8 @@ export { XlsxArtifactViewerComponent };
|
|
|
207
234
|
[fileName]="artifactVersion?.FileName || 'workbook.xlsx'"
|
|
208
235
|
[isDownloading]="isDownloading"
|
|
209
236
|
[showPrint]="false"
|
|
210
|
-
(download)="onDownload()"
|
|
237
|
+
(download)="onDownload()"
|
|
238
|
+
>
|
|
211
239
|
</mj-file-artifact-toolbar>
|
|
212
240
|
|
|
213
241
|
@if (isLoading) {
|
|
@@ -224,10 +252,7 @@ export { XlsxArtifactViewerComponent };
|
|
|
224
252
|
@if (sheets.length > 1) {
|
|
225
253
|
<div class="xlsx-viewer__tabs">
|
|
226
254
|
@for (sheet of sheets; track sheet.name; let i = $index) {
|
|
227
|
-
<button
|
|
228
|
-
class="xlsx-viewer__tab"
|
|
229
|
-
[class.xlsx-viewer__tab--active]="i === activeSheetIndex"
|
|
230
|
-
(click)="selectSheet(i)">
|
|
255
|
+
<button class="xlsx-viewer__tab" [class.xlsx-viewer__tab--active]="i === activeSheetIndex" (click)="selectSheet(i)">
|
|
231
256
|
<i class="fas fa-table"></i>
|
|
232
257
|
{{ sheet.name }}
|
|
233
258
|
</button>
|
|
@@ -244,12 +269,13 @@ export { XlsxArtifactViewerComponent };
|
|
|
244
269
|
[animateRows]="false"
|
|
245
270
|
[suppressMovableColumns]="false"
|
|
246
271
|
[enableCellTextSelection]="true"
|
|
247
|
-
(gridReady)="onGridReady($event)"
|
|
272
|
+
(gridReady)="onGridReady($event)"
|
|
273
|
+
>
|
|
248
274
|
</ag-grid-angular>
|
|
249
275
|
</div>
|
|
250
276
|
}
|
|
251
277
|
</div>
|
|
252
|
-
`, styles: ["\n
|
|
278
|
+
`, styles: ["\n .xlsx-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n flex: 1;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .xlsx-viewer__state--error {\n color: var(--mj-status-error-text);\n }\n\n .xlsx-viewer__tabs {\n display: flex;\n gap: 2px;\n padding: 4px 8px 0;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n overflow-x: auto;\n flex-shrink: 0;\n }\n\n .xlsx-viewer__tab {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n color: var(--mj-text-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n transition:\n background 0.1s,\n color 0.1s;\n }\n\n .xlsx-viewer__tab:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .xlsx-viewer__tab--active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border-color: var(--mj-border-default);\n font-weight: 600;\n position: relative;\n }\n\n .xlsx-viewer__tab--active::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: 1px;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__grid {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n .xlsx-viewer__grid ag-grid-angular {\n height: 100%;\n width: 100%;\n display: block;\n }\n "] }]
|
|
253
279
|
}], () => [{ type: i1.ArtifactFileService }, { type: i0.ChangeDetectorRef }], null); })();
|
|
254
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(XlsxArtifactViewerComponent, { className: "XlsxArtifactViewerComponent", filePath: "src/lib/components/plugins/xlsx-artifact-viewer.component.ts", lineNumber:
|
|
280
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(XlsxArtifactViewerComponent, { className: "XlsxArtifactViewerComponent", filePath: "src/lib/components/plugins/xlsx-artifact-viewer.component.ts", lineNumber: 162 }); })();
|
|
255
281
|
//# sourceMappingURL=xlsx-artifact-viewer.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xlsx-artifact-viewer.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/xlsx-artifact-viewer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnInit, ChangeDetectorRef } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';\nimport { ArtifactFileService } from '../../services/artifact-file.service';\nimport { ColDef, GridReadyEvent, GridApi } from 'ag-grid-community';\n\n/** A parsed sheet ready for display in AG Grid. */\ninterface SheetData {\n name: string;\n rowData: Record<string, string | number | boolean | null>[];\n columnDefs: ColDef[];\n}\n\n/**\n * Viewer plugin for Excel (XLSX/XLS) artifact versions stored in MJStorage.\n *\n * Downloads the file, parses all sheets with SheetJS, then renders the active\n * sheet in an AG Grid. Sheet tabs let the user switch between sheets.\n */\n@Component({\n standalone: false,\n selector: 'mj-xlsx-artifact-viewer',\n template: `\n <div class=\"xlsx-viewer\">\n <mj-file-artifact-toolbar\n [fileName]=\"artifactVersion?.FileName || 'workbook.xlsx'\"\n [isDownloading]=\"isDownloading\"\n [showPrint]=\"false\"\n (download)=\"onDownload()\">\n </mj-file-artifact-toolbar>\n\n @if (isLoading) {\n <div class=\"xlsx-viewer__state\">\n <i class=\"fas fa-spinner fa-spin\"></i>\n <span>Loading workbook…</span>\n </div>\n } @else if (errorMessage) {\n <div class=\"xlsx-viewer__state xlsx-viewer__state--error\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else {\n @if (sheets.length > 1) {\n <div class=\"xlsx-viewer__tabs\">\n @for (sheet of sheets; track sheet.name; let i = $index) {\n <button\n class=\"xlsx-viewer__tab\"\n [class.xlsx-viewer__tab--active]=\"i === activeSheetIndex\"\n (click)=\"selectSheet(i)\">\n <i class=\"fas fa-table\"></i>\n {{ sheet.name }}\n </button>\n }\n </div>\n }\n\n <div class=\"xlsx-viewer__grid\">\n <ag-grid-angular\n class=\"ag-theme-quartz\"\n [rowData]=\"activeSheet?.rowData\"\n [columnDefs]=\"activeSheet?.columnDefs\"\n [defaultColDef]=\"defaultColDef\"\n [animateRows]=\"false\"\n [suppressMovableColumns]=\"false\"\n [enableCellTextSelection]=\"true\"\n (gridReady)=\"onGridReady($event)\">\n </ag-grid-angular>\n </div>\n }\n </div>\n `,\n styles: [`\n .xlsx-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n flex: 1;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .xlsx-viewer__state--error {\n color: var(--mj-status-error-text);\n }\n\n .xlsx-viewer__tabs {\n display: flex;\n gap: 2px;\n padding: 4px 8px 0;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n overflow-x: auto;\n flex-shrink: 0;\n }\n\n .xlsx-viewer__tab {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n color: var(--mj-text-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.1s, color 0.1s;\n }\n\n .xlsx-viewer__tab:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .xlsx-viewer__tab--active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border-color: var(--mj-border-default);\n font-weight: 600;\n position: relative;\n }\n\n .xlsx-viewer__tab--active::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: 1px;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__grid {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n .xlsx-viewer__grid ag-grid-angular {\n height: 100%;\n width: 100%;\n display: block;\n }\n `]\n})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'XlsxArtifactViewerPlugin')\nexport class XlsxArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit {\n\n public isLoading = true;\n public isDownloading = false;\n public errorMessage = '';\n public sheets: SheetData[] = [];\n public activeSheetIndex = 0;\n public defaultColDef: ColDef = { resizable: true, sortable: true, filter: true, minWidth: 80 };\n\n private gridApi: GridApi | null = null;\n private downloadUrl = '';\n\n constructor(\n private fileService: ArtifactFileService,\n private cdr: ChangeDetectorRef,\n ) {\n super();\n }\n\n public override get hasDisplayContent(): boolean { return true; }\n\n public get activeSheet(): SheetData | null {\n return this.sheets[this.activeSheetIndex] ?? null;\n }\n\n async ngOnInit(): Promise<void> {\n await this.loadWorkbook();\n }\n\n public onGridReady(event: GridReadyEvent): void {\n this.gridApi = event.api;\n this.gridApi.sizeColumnsToFit();\n }\n\n public selectSheet(index: number): void {\n this.activeSheetIndex = index;\n this.cdr.markForCheck();\n // Let Angular render the new rowData/columnDefs before re-sizing\n setTimeout(() => this.gridApi?.sizeColumnsToFit(), 0);\n }\n\n public async onDownload(): Promise<void> {\n if (!this.downloadUrl || this.isDownloading) {\n return;\n }\n this.isDownloading = true;\n this.cdr.markForCheck();\n try {\n await this.triggerBrowserDownload(\n this.downloadUrl,\n this.artifactVersion?.FileName || 'workbook.xlsx',\n );\n } finally {\n this.isDownloading = false;\n this.cdr.markForCheck();\n }\n }\n\n // ─── Private helpers ────────────────────────────────────────────────────────\n\n private async loadWorkbook(): Promise<void> {\n if (!this.artifactVersion?.ID) {\n this.showError('No artifact version provided.');\n return;\n }\n\n try {\n let arrayBuffer: ArrayBuffer;\n\n if (this.artifactVersion.ContentMode === 'File') {\n // File-backed: download from storage via pre-auth URL\n this.downloadUrl = await this.fileService.getDownloadUrl(this.artifactVersion.ID);\n arrayBuffer = await this.fetchAsArrayBuffer(this.downloadUrl);\n } else {\n // Inline: content is a base64 data URL stored in the artifact version\n const content = this.artifactVersion.Content;\n if (!content) {\n this.showError('Artifact has no content.');\n return;\n }\n arrayBuffer = this.fileService.dataUrlToArrayBuffer(content);\n // Create an object URL for download support\n this.downloadUrl = this.fileService.dataUrlToObjectUrl(\n content,\n this.artifactVersion.MimeType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\n );\n }\n\n const XLSX = await import('xlsx') as unknown as XlsxModuleShim;\n const workbook = XLSX.read(arrayBuffer, { type: 'array' });\n this.sheets = this.parseWorkbook(workbook, XLSX);\n this.isLoading = false;\n this.cdr.markForCheck();\n } catch (err) {\n this.showError(`Could not load workbook: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n private parseWorkbook(workbook: WorkbookType, XLSX: XlsxModuleShim): SheetData[] {\n return workbook.SheetNames.map(name => {\n const sheet = workbook.Sheets[name];\n const rows = XLSX.utils.sheet_to_json<Record<string, string | number | boolean | null>>(sheet, {\n defval: null,\n raw: false, // Format dates/numbers as strings so AG Grid can display them\n });\n\n const headers = rows.length > 0 ? Object.keys(rows[0]) : [];\n const columnDefs: ColDef[] = headers.map(h => ({\n field: h,\n headerName: h,\n tooltipField: h,\n }));\n\n return { name, rowData: rows, columnDefs };\n });\n }\n\n private async fetchAsArrayBuffer(url: string): Promise<ArrayBuffer> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} fetching file`);\n }\n return response.arrayBuffer();\n }\n\n private showError(message: string): void {\n this.isLoading = false;\n this.errorMessage = message;\n this.cdr.markForCheck();\n }\n\n}\n\n\n// ─── Minimal type shims for xlsx dynamic import ────────────────────────────────\n\ninterface WorkbookType {\n SheetNames: string[];\n Sheets: Record<string, unknown>;\n}\n\ninterface XlsxModuleShim {\n read(data: ArrayBuffer, opts: { type: 'array' | 'buffer' | 'binary' | 'base64' | 'string' }): WorkbookType;\n utils: {\n sheet_to_json<T>(sheet: unknown, opts: { defval: null; raw: boolean }): T[];\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"xlsx-artifact-viewer.component.js","sourceRoot":"","sources":["../../../../src/lib/components/plugins/xlsx-artifact-viewer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnInit, ChangeDetectorRef } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { DataSnapshot, DataTable, MJColumnDescriptor } from '@memberjunction/core';\nimport { BaseArtifactViewerPluginComponent } from '../base-artifact-viewer.component';\nimport { ArtifactFileService } from '../../services/artifact-file.service';\nimport { ColDef, GridReadyEvent, GridApi } from 'ag-grid-community';\n\n/** A parsed sheet ready for display in AG Grid. */\ninterface SheetData {\n name: string;\n rowData: Record<string, string | number | boolean | null>[];\n columnDefs: ColDef[];\n}\n\n/**\n * Viewer plugin for Excel (XLSX/XLS) artifact versions stored in MJStorage.\n *\n * Downloads the file, parses all sheets with SheetJS, then renders the active\n * sheet in an AG Grid. Sheet tabs let the user switch between sheets.\n */\n@Component({\n standalone: false,\n selector: 'mj-xlsx-artifact-viewer',\n template: `\n <div class=\"xlsx-viewer\">\n <mj-file-artifact-toolbar\n [fileName]=\"artifactVersion?.FileName || 'workbook.xlsx'\"\n [isDownloading]=\"isDownloading\"\n [showPrint]=\"false\"\n (download)=\"onDownload()\"\n >\n </mj-file-artifact-toolbar>\n\n @if (isLoading) {\n <div class=\"xlsx-viewer__state\">\n <i class=\"fas fa-spinner fa-spin\"></i>\n <span>Loading workbook…</span>\n </div>\n } @else if (errorMessage) {\n <div class=\"xlsx-viewer__state xlsx-viewer__state--error\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else {\n @if (sheets.length > 1) {\n <div class=\"xlsx-viewer__tabs\">\n @for (sheet of sheets; track sheet.name; let i = $index) {\n <button class=\"xlsx-viewer__tab\" [class.xlsx-viewer__tab--active]=\"i === activeSheetIndex\" (click)=\"selectSheet(i)\">\n <i class=\"fas fa-table\"></i>\n {{ sheet.name }}\n </button>\n }\n </div>\n }\n\n <div class=\"xlsx-viewer__grid\">\n <ag-grid-angular\n class=\"ag-theme-quartz\"\n [rowData]=\"activeSheet?.rowData\"\n [columnDefs]=\"activeSheet?.columnDefs\"\n [defaultColDef]=\"defaultColDef\"\n [animateRows]=\"false\"\n [suppressMovableColumns]=\"false\"\n [enableCellTextSelection]=\"true\"\n (gridReady)=\"onGridReady($event)\"\n >\n </ag-grid-angular>\n </div>\n }\n </div>\n `,\n styles: [\n `\n .xlsx-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n flex: 1;\n color: var(--mj-text-muted);\n font-size: 14px;\n }\n\n .xlsx-viewer__state--error {\n color: var(--mj-status-error-text);\n }\n\n .xlsx-viewer__tabs {\n display: flex;\n gap: 2px;\n padding: 4px 8px 0;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n overflow-x: auto;\n flex-shrink: 0;\n }\n\n .xlsx-viewer__tab {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n color: var(--mj-text-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n transition:\n background 0.1s,\n color 0.1s;\n }\n\n .xlsx-viewer__tab:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .xlsx-viewer__tab--active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border-color: var(--mj-border-default);\n font-weight: 600;\n position: relative;\n }\n\n .xlsx-viewer__tab--active::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: 1px;\n background: var(--mj-bg-surface);\n }\n\n .xlsx-viewer__grid {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n .xlsx-viewer__grid ag-grid-angular {\n height: 100%;\n width: 100%;\n display: block;\n }\n `,\n ],\n})\n@RegisterClass(BaseArtifactViewerPluginComponent, 'XlsxArtifactViewerPlugin')\nexport class XlsxArtifactViewerComponent extends BaseArtifactViewerPluginComponent implements OnInit {\n public isLoading = true;\n public isDownloading = false;\n public errorMessage = '';\n public sheets: SheetData[] = [];\n public activeSheetIndex = 0;\n public defaultColDef: ColDef = { resizable: true, sortable: true, filter: true, minWidth: 80 };\n\n private gridApi: GridApi | null = null;\n private downloadUrl = '';\n\n constructor(\n private fileService: ArtifactFileService,\n private cdr: ChangeDetectorRef,\n ) {\n super();\n }\n\n public override get hasDisplayContent(): boolean {\n return true;\n }\n\n public get activeSheet(): SheetData | null {\n return this.sheets[this.activeSheetIndex] ?? null;\n }\n\n async ngOnInit(): Promise<void> {\n await this.loadWorkbook();\n }\n\n public onGridReady(event: GridReadyEvent): void {\n this.gridApi = event.api;\n this.gridApi.sizeColumnsToFit();\n }\n\n public selectSheet(index: number): void {\n this.activeSheetIndex = index;\n this.cdr.markForCheck();\n // Let Angular render the new rowData/columnDefs before re-sizing\n setTimeout(() => this.gridApi?.sizeColumnsToFit(), 0);\n }\n\n public async onDownload(): Promise<void> {\n if (!this.downloadUrl || this.isDownloading) {\n return;\n }\n this.isDownloading = true;\n this.cdr.markForCheck();\n try {\n await this.triggerBrowserDownload(this.downloadUrl, this.artifactVersion?.FileName || 'workbook.xlsx');\n } finally {\n this.isDownloading = false;\n this.cdr.markForCheck();\n }\n }\n\n // ─── Private helpers ────────────────────────────────────────────────────────\n\n private async loadWorkbook(): Promise<void> {\n if (!this.artifactVersion?.ID) {\n this.showError('No artifact version provided.');\n return;\n }\n\n try {\n let arrayBuffer: ArrayBuffer;\n\n if (this.artifactVersion.ContentMode === 'File') {\n // File-backed: download from storage via pre-auth URL\n this.downloadUrl = await this.fileService.getDownloadUrl(this.artifactVersion.ID);\n arrayBuffer = await this.fetchAsArrayBuffer(this.downloadUrl);\n } else {\n // Inline: content is a base64 data URL stored in the artifact version\n const content = this.artifactVersion.Content;\n if (!content) {\n this.showError('Artifact has no content.');\n return;\n }\n arrayBuffer = this.fileService.dataUrlToArrayBuffer(content);\n // Create an object URL for download support\n this.downloadUrl = this.fileService.dataUrlToObjectUrl(\n content,\n this.artifactVersion.MimeType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n );\n }\n\n const XLSX = (await import('xlsx')) as unknown as XlsxModuleShim;\n const workbook = XLSX.read(arrayBuffer, { type: 'array' });\n this.sheets = this.parseWorkbook(workbook, XLSX);\n this.isLoading = false;\n this.cdr.markForCheck();\n } catch (err) {\n this.showError(`Could not load workbook: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n private parseWorkbook(workbook: WorkbookType, XLSX: XlsxModuleShim): SheetData[] {\n return workbook.SheetNames.map((name) => {\n const sheet = workbook.Sheets[name];\n const rows = XLSX.utils.sheet_to_json<Record<string, string | number | boolean | null>>(sheet, {\n defval: null,\n raw: false, // Format dates/numbers as strings so AG Grid can display them\n });\n\n const headers = rows.length > 0 ? Object.keys(rows[0]) : [];\n const columnDefs: ColDef[] = headers.map((h) => ({\n field: h,\n headerName: h,\n tooltipField: h,\n }));\n\n return { name, rowData: rows, columnDefs };\n });\n }\n\n private async fetchAsArrayBuffer(url: string): Promise<ArrayBuffer> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} fetching file`);\n }\n return response.arrayBuffer();\n }\n\n private showError(message: string): void {\n this.isLoading = false;\n this.errorMessage = message;\n this.cdr.markForCheck();\n }\n\n public override GetCurrentStateSnapshot(): DataSnapshot | null {\n if (this.sheets.length === 0) return null;\n\n // Convert parsed sheets into DataTables for structured snapshot\n const tables: DataTable[] = this.sheets.map((sheet) => {\n const table = new DataTable();\n table.name = sheet.name;\n table.source = 'static';\n table.columns = sheet.columnDefs\n .filter((col) => col.field)\n .map((col) => {\n const desc = new MJColumnDescriptor(col.field as string);\n desc.displayName = (col.headerName as string | undefined) ?? (col.field as string);\n desc.sqlBaseType = 'nvarchar';\n return desc;\n });\n table.rows = sheet.rowData;\n table.metadata = { rowCount: sheet.rowData.length };\n return table;\n });\n\n const snap = DataSnapshot.FromTables(tables, this.getDisplayTitle() ?? undefined);\n snap.activeTab = this.sheets[this.activeSheetIndex]?.name;\n return snap;\n }\n}\n\n// ─── Minimal type shims for xlsx dynamic import ────────────────────────────────\n\ninterface WorkbookType {\n SheetNames: string[];\n Sheets: Record<string, unknown>;\n}\n\ninterface XlsxModuleShim {\n read(data: ArrayBuffer, opts: { type: 'array' | 'buffer' | 'binary' | 'base64' | 'string' }): WorkbookType;\n utils: {\n sheet_to_json<T>(sheet: unknown, opts: { defval: null; raw: boolean }): T[];\n };\n}\n"]}
|