UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

591 lines 71.5 kB
import * as i0 from '@angular/core';
import { Injectable, inject, Input, Component, Injector, runInInjectionContext, signal, ViewChild } from '@angular/core';
import { DashboardDetailService } from '@c8y/ngx-components/context-dashboard';
import * as i4$1 from 'ngx-bootstrap/popover';
import { PopoverModule } from 'ngx-bootstrap/popover';
import { gettext } from '@c8y/ngx-components/gettext';
import * as i1 from '@c8y/ngx-components';
import { CoreModule, DynamicComponentService, AlertService, CommonModule, FormsModule } from '@c8y/ngx-components';
import { EditorComponent } from '@c8y/ngx-components/editor';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { Subject, debounceTime, takeUntil } from 'rxjs';
import * as i3$1 from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { cloneDeep } from 'lodash-es';
import { saveAs } from 'file-saver';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import * as i3 from '@c8y/ngx-components/assets-navigator';
import { AssetSelectorModule } from '@c8y/ngx-components/assets-navigator';
import * as i4 from 'ngx-bootstrap/dropdown';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import * as i5 from 'ngx-bootstrap/tooltip';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import * as i2 from '@angular/common';

class JsonValidationService {
    constructor(dynamicComponentService) {
        this.dynamicComponentService = dynamicComponentService;
        this.OMIT_ERRORS = ['must NOT be valid', 'must match exactly one schema in oneOf'];
        this.ajv = new Ajv({ verbose: true, allErrors: true });
        addFormats(this.ajv);
    }
    async validateDashboard(dashboardJson, schemas) {
        try {
            // Parse JSON string to object
            const dashboard = JSON.parse(dashboardJson);
            // Get or use provided schemas
            const validationSchemas = schemas || (await this.getJsonSchemas(dashboard));
            // Create a new Ajv instance
            const validator = new Ajv({
                verbose: true,
                allErrors: true
            });
            addFormats(validator);
            // Add all component schemas individually
            validationSchemas.forEach(({ uri, schema }) => {
                validator.addSchema(schema, uri);
            });
            const errorsMap = new Map();
            // Validate main dashboard structure first (without children)
            const mainSchema = validationSchemas.find(s => s.uri === 'context-dashboard.c8y-schema-loader')?.schema;
            if (!mainSchema) {
                throw new Error('Dashboard schema not found');
            }
            // Create a copy without children for main validation
            const dashboardWithoutChildren = { ...dashboard };
            delete dashboardWithoutChildren.children;
            const mainValidate = validator.compile(mainSchema);
            mainValidate(dashboardWithoutChildren);
            // Now validate each widget with its specific schema
            if (dashboard.children) {
                Object.entries(dashboard.children).forEach(([widgetId, widget]) => {
                    const componentId = widget.componentId;
                    // Find the correct schema for this widget type
                    const widgetSchema = validationSchemas.find(s => s.uri === componentId)?.schema;
                    if (widgetSchema) {
                        // Validate this widget's config against its specific schema
                        const validateWidgetConfig = validator.compile(widgetSchema);
                        const isValid = validateWidgetConfig(widget.config);
                        if (!isValid && validateWidgetConfig.errors) {
                            validateWidgetConfig.errors.forEach(error => {
                                const message = error.message || 'Unknown error';
                                const basePath = `/children/${widgetId}/config`;
                                const relativePath = error.instancePath;
                                const fullPath = basePath + relativePath;
                                if (!this.OMIT_ERRORS.includes(message)) {
                                    errorsMap.set(`${message}::${fullPath}`, {
                                        message,
                                        path: fullPath
                                    });
                                }
                            });
                        }
                    }
                });
            }
            // Process any errors from main validation
            if (mainValidate.errors) {
                mainValidate.errors.forEach(error => {
                    const message = error.message || 'Unknown error';
                    const path = error.instancePath;
                    // Skip children validation errors as we handled them separately
                    if (!path.includes('/children/') && !this.OMIT_ERRORS.includes(message)) {
                        errorsMap.set(`${message}::${path}`, { message, path });
                    }
                });
            }
            return Array.from(errorsMap.values());
        }
        catch (error) {
            if (error instanceof SyntaxError) {
                return [{ message: 'Invalid JSON syntax', path: '' }];
            }
            throw error;
        }
    }
    /**
     * Returns JSON schema for dashboard and all its children.
     * Children are referenced by their componentId. Dashboard schema is enriched with references to children schemas.
     * @param dashboard Context dashboard object.
     */
    async getJsonSchemas(dashboard) {
        try {
            const dashboardChildrenIds = [
                ...new Set(Object.entries(dashboard.children || {}).map(([_, value]) => value.componentId))
            ];
            const widgetSchemasPromises = dashboardChildrenIds.map(id => this.getSchemaForWidget(id));
            const widgetSchemasResults = await Promise.allSettled(widgetSchemasPromises);
            // Filter out failed or null schemas
            const validWidgetSchemas = widgetSchemasResults
                .filter((result) => result.status === 'fulfilled' && result.value !== null)
                .map(result => result.value);
            const { schema: dashboardSchemaOriginal } = await import('c8y-schema-loader?interfaceName=ContextDashboard&type=dashboarding!@c8y/ngx-components/context-dashboard');
            const dashboardSchema = JSON.parse(JSON.stringify(dashboardSchemaOriginal));
            // Modify Widget definition with proper schema structure
            if (validWidgetSchemas.length > 0) {
                // Remove any existing allOf or oneOf conditions that might interfere
                if (dashboardSchema.definitions.Widget.allOf) {
                    delete dashboardSchema.definitions.Widget.allOf;
                }
                // Set up the oneOf condition
                dashboardSchema.definitions.Widget.oneOf = this.getWidgetSchemasReferences(validWidgetSchemas.map(({ id }) => id));
            }
            return [
                {
                    uri: 'context-dashboard.c8y-schema-loader',
                    fileMatch: ['*'],
                    schema: dashboardSchema
                },
                ...validWidgetSchemas.map(({ id, schema }) => ({
                    uri: id,
                    schema: schema
                }))
            ];
        }
        catch (error) {
            console.error('Error processing dashboard schemas:', error);
            throw error;
        }
    }
    async getSchemaForWidget(id) {
        try {
            const def = await this.dynamicComponentService.getById(id);
            if (def?.data?.schema && typeof def.data.schema === 'function') {
                const { schema } = await def.data.schema();
                return { id, schema };
            }
            return null;
        }
        catch (error) {
            console.warn(`Failed to fetch schema for widget ${id}:`, error);
            return null;
        }
    }
    getWidgetSchemasReferences(widgetIds) {
        // provide references to all children schemas
        const schemasPart = widgetIds.map(id => {
            return {
                properties: {
                    componentId: { const: id },
                    config: { $ref: `${id}#` } // Ensure proper URI reference format
                },
                required: ['componentId'] // Only require componentId
            };
        });
        // provide default schema for object if component has no schema definition
        const defaultPart = {
            not: {
                properties: {
                    componentId: {
                        enum: widgetIds
                    }
                }
            },
            properties: {
                config: {
                    type: 'object',
                    additionalProperties: true
                }
            }
        };
        return [...schemasPart, defaultPart];
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: JsonValidationService, deps: [{ token: i1.DynamicComponentService }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: JsonValidationService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: JsonValidationService, decorators: [{
            type: Injectable
        }], ctorParameters: () => [{ type: i1.DynamicComponentService }] });

class AssignWidgetAssetModalComponent {
    constructor() {
        this.notSupportedWidgets = [];
        this.modalTitle = gettext(`
      For each asset in the widgets, we suggest a source based on the original configuration. 
      You can accept it, choose another asset, or configure it later. If you don’t choose any option,
      the widget will not be imported.
      `);
        this.assetIdInfo = gettext('id: {{ assetId }}');
        this.acceptSuggestedLabel = gettext('Accept suggested asset');
        this.noSuggestedAssetLabel = gettext('No suggestion available');
        this.updatedWidgetAssets = [];
        this.userClickedAcceptAllSuggested = false;
        this.assetSelectedLabel = gettext('Asset selected');
        this.assetNotSelectedLabel = gettext('Asset not selected');
        this.result = new Promise((resolve, reject) => {
            this._close = resolve;
            this._cancel = reject;
        });
        this.bsModalRef = inject(BsModalRef);
    }
    ngOnInit() {
        this.updatedWidgetAssets = this.assetsToAlign.map(asset => ({
            ...asset
        }));
        this.hasSuggestedAssets = this.updatedWidgetAssets.some(asset => !!asset.deviceRef.value.suggestedDevice);
    }
    apply() {
        const alignedAssets = this.updatedWidgetAssets.map(asset => {
            return {
                widgetId: asset.widgetId,
                title: asset.title,
                deviceRef: {
                    path: asset.deviceRef.path,
                    value: asset.selectedAsset == null
                        ? asset.selectedAsset
                        : { ...asset.selectedAsset, suggestedDevice: null }
                }
            };
        });
        this._close(alignedAssets);
        this.bsModalRef.hide();
    }
    acceptAllSuggested() {
        this.userClickedAcceptAllSuggested = true;
        this.updatedWidgetAssets.forEach(asset => {
            if (asset.deviceRef.value.suggestedDevice) {
                asset.selectedAsset = asset.deviceRef.value.suggestedDevice;
            }
        });
    }
    cancel() {
        this.bsModalRef.hide();
        this._cancel();
    }
    selectionChange($event, widgetAsset) {
        widgetAsset.selectedAsset = $event.change.item;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AssignWidgetAssetModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AssignWidgetAssetModalComponent, isStandalone: true, selector: "c8y-assign-widget-asset-modal", inputs: { assetsToAlign: "assetsToAlign", contextAsset: "contextAsset", notSupportedWidgets: "notSupportedWidgets" }, ngImport: i0, template: "<div class=\"viewport-modal has-asset-selector\">\n  <div class=\"modal-header dialog-header\">\n    <i [c8yIcon]=\"'th-large'\"></i>\n    <div\n      class=\"modal-title\"\n      id=\"modal-title\"\n      translate\n    >\n      Select and confirm widget assets mapping\n    </div>\n  </div>\n  <div\n    class=\"inner-scroll\"\n    id=\"modal-body\"\n  >\n    <div class=\"p-16\">\n      <p class=\"text-center text-balance\">\n        {{ modalTitle | translate }}\n      </p>\n    </div>\n    @if (notSupportedWidgets.length) {\n      <div class=\"d-flex fit-w j-c-center p-16 p-t-0\">\n        <div class=\"alert alert-warning\">\n          {{ 'Widgets not supported for asset mapping: {{ notSupportedWidgets }}' | translate: {\n          notSupportedWidgets: notSupportedWidgets.join(', ') } }}\n        </div>\n      </div>\n    }\n    @if (!assetsToAlign.length) {\n      <div class=\"p-16 d-flex j-c-center\">\n        <c8y-ui-empty-state\n          class=\"\"\n          [icon]=\"'empty-box'\"\n          [title]=\"'No assets to assign' | translate\"\n        ></c8y-ui-empty-state>\n      </div>\n    }\n    @if (assetsToAlign.length) {\n      <div>\n        <c8y-list-group class=\"separator-top no-border-last\">\n          <c8y-li class=\"sticky-top bg-level-1 hidden-sm hidden-xs\">\n            <c8y-li-icon></c8y-li-icon>\n            <div class=\"d-flex\">\n              <div class=\"col-md-2\">\n                <!-- title -->\n                <p\n                  class=\"text-truncate text-medium\"\n                  translate\n                >\n                  Widget\n                </p>\n              </div>\n              <div class=\"col-md-10\">\n                <div class=\"row\">\n                  <div class=\"col-md-4 p-r-0\">\n                    <p\n                      class=\"text-medium\"\n                      translate\n                    >\n                      Original source\n                    </p>\n                  </div>\n                  <div class=\"col-md-1 text-center\">\n                    <button\n                      class=\"btn btn-default btn-sm\"\n                      [attr.aria-label]=\"'Accept all suggested' | translate\"\n                      [tooltip]=\"'Accept all suggested' | translate\"\n                      placement=\"right\"\n                      (click)=\"acceptAllSuggested()\"\n                      [disabled]=\"!hasSuggestedAssets\"\n                    >\n                      <i [c8yIcon]=\"'check'\"></i>\n                    </button>\n                  </div>\n                  <div class=\"col-md-7 p-l-8\">\n                    <p\n                      class=\"text-medium\"\n                      translate\n                    >\n                      Selected source\n                    </p>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </c8y-li>\n          @for (widgetAsset of updatedWidgetAssets; track widgetAsset; let i = $index) {\n            <c8y-li class=\"c8y-list__item--overflow-visible\">\n              <c8y-li-icon\n                class=\"p-r-0 p-t-16\"\n                icon=\"th-large\"\n              ></c8y-li-icon>\n              <div class=\"d-flex-md\">\n                <div class=\"col-md-2\">\n                  <!-- title -->\n                  <p\n                    class=\"text-truncate text-medium boxed-label p-l-0\"\n                    title=\"{{ widgetAsset.title }}\"\n                  >\n                    {{ widgetAsset.title }}\n                  </p>\n                </div>\n                <div class=\"col-md-10\">\n                  <div class=\"row\">\n                    <div class=\"col-md-4 p-l-md-0 p-r-0\">\n                      <!-- origin device -->\n                      <p class=\"visible-sm visible-xs text-label-small\">\n                        {{ 'Original source' | translate }}\n                      </p>\n                      <div class=\"d-flex gap-4 boxed-label\">\n                        <ng-container\n                          *ngTemplateOutlet=\"\n                            assetIcon;\n                            context: { $implicit: widgetAsset.deviceRef.value }\n                          \"\n                        ></ng-container>\n                        <span class=\"text-truncate\">\n                          <span\n                            class=\"text-truncate\"\n                            title=\"{{ widgetAsset.deviceRef.value.name }}\"\n                          >\n                            {{ widgetAsset.deviceRef.value.name }}\n                          </span>\n                          <small class=\"text-muted\">\n                            {{\n                              assetIdInfo | translate: { assetId: widgetAsset.deviceRef.value.id }\n                            }}\n                          </small>\n                        </span>\n                      </div>\n                    </div>\n                    <div\n                      class=\"col-md-1 col-xs-1 p-l-xs-0 p-l-sm-0 p-r-sm-0 p-r-xs-0 text-center p-t-16\"\n                    >\n                      <i\n                        class=\"icon-20\"\n                        [c8yIcon]=\"\n                          widgetAsset.selectedAsset !== undefined ? 'ok' : 'inactive-state'\n                        \"\n                        [title]=\"\n                          widgetAsset.selectedAsset !== undefined\n                            ? (assetSelectedLabel | translate)\n                            : (assetNotSelectedLabel | translate)\n                        \"\n                        [ngClass]=\"{\n                          'text-success': widgetAsset.selectedAsset !== undefined,\n                          'text-muted': widgetAsset.selectedAsset === undefined\n                        }\"\n                      ></i>\n                      @if (\n                        widgetAsset.selectedAsset === undefined && userClickedAcceptAllSuggested\n                      ) {\n                        <span\n                          class=\"c8y-icon-badge\"\n                          [tooltip]=\"\n                            'No suggestion available - widget will not be imported' | translate\n                          \"\n                          placement=\"right\"\n                        >\n                          <i\n                            class=\"status major stroked-icon\"\n                            c8yIcon=\"warning\"\n                          ></i>\n                        </span>\n                      }\n                    </div>\n                    <div class=\"col-md-4 col-xs-11 p-l-md-0\">\n                      <!-- Suggested/Selected source -->\n                      <p class=\"visible-sm visible-xs text-label-small m-t-16 m-b-8\">\n                        {{ 'Selected source' | translate }}\n                      </p>\n                      @if (\n                        widgetAsset.deviceRef.value.suggestedDevice && !widgetAsset.selectedAsset\n                      ) {\n                        <div\n                          class=\"d-flex gap-4 text-muted boxed-label\"\n                          [attr.data-label]=\"'Suggested' | translate\"\n                        >\n                          <ng-container\n                            *ngTemplateOutlet=\"\n                              assetIcon;\n                              context: { $implicit: widgetAsset.deviceRef.value.suggestedDevice }\n                            \"\n                          ></ng-container>\n                          <span class=\"text-truncate\">\n                            <span\n                              class=\"text-truncate\"\n                              title=\"{{ widgetAsset.deviceRef.value.suggestedDevice?.name }}\"\n                            >\n                              {{ widgetAsset.deviceRef.value.suggestedDevice?.name }}\n                            </span>\n                            <small class=\"text-muted\">\n                              {{\n                                assetIdInfo\n                                  | translate\n                                    : { assetId: widgetAsset.deviceRef.value.suggestedDevice?.id }\n                              }}\n                            </small>\n                          </span>\n                        </div>\n                      }\n                      @if (widgetAsset.selectedAsset) {\n                        <div class=\"d-flex gap-4 boxed-label\">\n                          <!-- selectedAsset icon -->\n                          <ng-container\n                            *ngTemplateOutlet=\"\n                              assetIcon;\n                              context: { $implicit: widgetAsset.selectedAsset }\n                            \"\n                          ></ng-container>\n                          <!-- selectedAsset Name and ID -->\n                          <span class=\"text-truncate\">\n                            <span\n                              class=\"text-truncate\"\n                              title=\"{{ widgetAsset.selectedAsset?.name }}\"\n                            >\n                              {{ widgetAsset.selectedAsset?.name }}\n                            </span>\n                            <small class=\"text-muted\">\n                              {{\n                                assetIdInfo | translate: { assetId: widgetAsset.selectedAsset.id }\n                              }}\n                            </small>\n                          </span>\n                        </div>\n                      }\n                      @if (\n                        !widgetAsset.deviceRef.value.suggestedDevice && !widgetAsset.selectedAsset\n                      ) {\n                        <div class=\"text-muted boxed-label\">\n                          <em>{{ 'No suggestion available' | translate }}</em>\n                        </div>\n                      }\n                    </div>\n                    <div class=\"col-md-3 p-0 flex-grow\">\n                      <div class=\"d-flex a-i-center j-c-end-md gap-8 boxed-label p-l-md-0 p-r-0\">\n                        <div class=\"input-group max-width-fit\">\n                          <div class=\"input-group-btn\">\n                            <button\n                              class=\"btn btn-default\"\n                              [attr.aria-label]=\"\n                                (widgetAsset.deviceRef.value.suggestedDevice\n                                  ? acceptSuggestedLabel\n                                  : noSuggestedAssetLabel\n                                ) | translate\n                              \"\n                              [tooltip]=\"\n                                (widgetAsset.deviceRef.value.suggestedDevice\n                                  ? acceptSuggestedLabel\n                                  : noSuggestedAssetLabel\n                                ) | translate\n                              \"\n                              placement=\"top\"\n                              container=\"body\"\n                              [ngClass]=\"{\n                                active:\n                                  widgetAsset.selectedAsset &&\n                                  widgetAsset.selectedAsset ===\n                                    widgetAsset.deviceRef.value.suggestedDevice\n                              }\"\n                              [disabled]=\"!widgetAsset.deviceRef.value.suggestedDevice\"\n                              (click)=\"\n                                widgetAsset.selectedAsset =\n                                  widgetAsset.deviceRef.value.suggestedDevice\n                              \"\n                            >\n                              <i [c8yIcon]=\"'check'\"></i>\n                            </button>\n                          </div>\n                          <div\n                            class=\"dropdown input-group-btn\"\n                            container=\"body\"\n                            dropdown\n                            [insideClick]=\"true\"\n                            #dropdown=\"bs-dropdown\"\n                          >\n                            <button\n                              class=\"btn btn-default dropdown-toggle c8y-dropdown\"\n                              [attr.aria-label]=\"'Select asset' | translate\"\n                              tooltip=\"{{ 'Select asset' | translate }}\"\n                              placement=\"top\"\n                              container=\"body\"\n                              type=\"button\"\n                              dropdownToggle\n                              [ngClass]=\"{\n                                active:\n                                  widgetAsset.selectedAsset &&\n                                  widgetAsset.selectedAsset !==\n                                    widgetAsset.deviceRef.value.suggestedDevice\n                              }\"\n                            >\n                              <i [c8yIcon]=\"'card-exchange'\"></i>\n                            </button>\n                            <div\n                              class=\"dropdown-menu dropdown-menu-right--md\"\n                              style=\"width: 300px\"\n                              *dropdownMenu\n                            >\n                              @if (dropdown.isOpen) {\n                                <c8y-asset-selector-miller\n                                  (onSelected)=\"\n                                    selectionChange($event, widgetAsset); dropdown.hide()\n                                  \"\n                                  [asset]=\"contextAsset\"\n                                  [config]=\"{\n                                    groupsSelectable: true,\n                                    showChildDevices: true,\n                                    showFilter: true,\n                                    showSelected: false,\n                                    columnHeaders: true,\n                                    singleColumn: true,\n                                    preventInitialSelect: true\n                                  }\"\n                                ></c8y-asset-selector-miller>\n                              }\n                            </div>\n                          </div>\n                          <div class=\"input-group-btn\">\n                            <button\n                              class=\"btn btn-default\"\n                              [attr.aria-label]=\"'Configure later' | translate\"\n                              tooltip=\"{{ 'Configure later' | translate }}\"\n                              placement=\"top\"\n                              container=\"body\"\n                              [ngClass]=\"{ active: widgetAsset.selectedAsset === null }\"\n                              (click)=\"widgetAsset.selectedAsset = null\"\n                            >\n                              <i [c8yIcon]=\"'link-off'\"></i>\n                            </button>\n                          </div>\n                        </div>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </c8y-li>\n          }\n        </c8y-list-group>\n      </div>\n    }\n  </div>\n  <div class=\"modal-footer\">\n    <button\n      class=\"btn btn-default\"\n      [title]=\"'Cancel' | translate\"\n      type=\"button\"\n      (click)=\"cancel()\"\n    >\n      {{ 'Cancel' | translate }}\n    </button>\n    <button\n      class=\"btn btn-primary\"\n      [title]=\"'Apply' | translate\"\n      type=\"button\"\n      (click)=\"apply()\"\n    >\n      {{ 'Apply' | translate }}\n    </button>\n  </div>\n</div>\n\n<ng-template\n  #assetIcon\n  let-device\n>\n  @if (device.c8y_IsDevice) {\n    <i\n      class=\"l-h-inherit\"\n      [c8yIcon]=\"'data-transfer'\"\n    ></i>\n  }\n  @if (device.c8y_IsDeviceGroup && !device.c8y_IsAsset) {\n    <i\n      class=\"l-h-inherit\"\n      [c8yIcon]=\"'c8y-group'\"\n    ></i>\n  }\n  @if (device.c8y_IsAsset) {\n    <i\n      class=\"l-h-inherit\"\n      [c8yIcon]=\"device.icon?.name || 'c8y-group'\"\n    ></i>\n  }\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CoreModule }, { kind: "component", type: i1.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i1.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i1.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i1.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i1.ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "ngmodule", type: AssetSelectorModule }, { kind: "component", type: i3.MillerViewComponent, selector: "c8y-asset-selector-miller", inputs: ["config", "asset", "selectedDevice", "rootNode", "container"], outputs: ["onSelected", "onClearSelected"] }, { kind: "ngmodule", type: BsDropdownModule }, { kind: "directive", type: i4.BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "directive", type: i4.BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: i4.BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "pipe", type: i1.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AssignWidgetAssetModalComponent, decorators: [{
            type: Component,
            args: [{ selector: 'c8y-assign-widget-asset-modal', standalone: true, imports: [CoreModule, AssetSelectorModule, BsDropdownModule, TooltipModule], template: "<div class=\"viewport-modal has-asset-selector\">\n  <div class=\"modal-header dialog-header\">\n    <i [c8yIcon]=\"'th-large'\"></i>\n    <div\n      class=\"modal-title\"\n      id=\"modal-title\"\n      translate\n    >\n      Select and confirm widget assets mapping\n    </div>\n  </div>\n  <div\n    class=\"inner-scroll\"\n    id=\"modal-body\"\n  >\n    <div class=\"p-16\">\n      <p class=\"text-center text-balance\">\n        {{ modalTitle | translate }}\n      </p>\n    </div>\n    @if (notSupportedWidgets.length) {\n      <div class=\"d-flex fit-w j-c-center p-16 p-t-0\">\n        <div class=\"alert alert-warning\">\n          {{ 'Widgets not supported for asset mapping: {{ notSupportedWidgets }}' | translate: {\n          notSupportedWidgets: notSupportedWidgets.join(', ') } }}\n        </div>\n      </div>\n    }\n    @if (!assetsToAlign.length) {\n      <div class=\"p-16 d-flex j-c-center\">\n        <c8y-ui-empty-state\n          class=\"\"\n          [icon]=\"'empty-box'\"\n          [title]=\"'No assets to assign' | translate\"\n        ></c8y-ui-empty-state>\n      </div>\n    }\n    @if (assetsToAlign.length) {\n      <div>\n        <c8y-list-group class=\"separator-top no-border-last\">\n          <c8y-li class=\"sticky-top bg-level-1 hidden-sm hidden-xs\">\n            <c8y-li-icon></c8y-li-icon>\n            <div class=\"d-flex\">\n              <div class=\"col-md-2\">\n                <!-- title -->\n                <p\n                  class=\"text-truncate text-medium\"\n                  translate\n                >\n                  Widget\n                </p>\n              </div>\n              <div class=\"col-md-10\">\n                <div class=\"row\">\n                  <div class=\"col-md-4 p-r-0\">\n                    <p\n                      class=\"text-medium\"\n                      translate\n                    >\n                      Original source\n                    </p>\n                  </div>\n                  <div class=\"col-md-1 text-center\">\n                    <button\n                      class=\"btn btn-default btn-sm\"\n                      [attr.aria-label]=\"'Accept all suggested' | translate\"\n                      [tooltip]=\"'Accept all suggested' | translate\"\n                      placement=\"right\"\n                      (click)=\"acceptAllSuggested()\"\n                      [disabled]=\"!hasSuggestedAssets\"\n                    >\n                      <i [c8yIcon]=\"'check'\"></i>\n                    </button>\n                  </div>\n                  <div class=\"col-md-7 p-l-8\">\n                    <p\n                      class=\"text-medium\"\n                      translate\n                    >\n                      Selected source\n                    </p>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </c8y-li>\n          @for (widgetAsset of updatedWidgetAssets; track widgetAsset; let i = $index) {\n            <c8y-li class=\"c8y-list__item--overflow-visible\">\n              <c8y-li-icon\n                class=\"p-r-0 p-t-16\"\n                icon=\"th-large\"\n              ></c8y-li-icon>\n              <div class=\"d-flex-md\">\n                <div class=\"col-md-2\">\n                  <!-- title -->\n                  <p\n                    class=\"text-truncate text-medium boxed-label p-l-0\"\n                    title=\"{{ widgetAsset.title }}\"\n                  >\n                    {{ widgetAsset.title }}\n                  </p>\n                </div>\n                <div class=\"col-md-10\">\n                  <div class=\"row\">\n                    <div class=\"col-md-4 p-l-md-0 p-r-0\">\n                      <!-- origin device -->\n                      <p class=\"visible-sm visible-xs text-label-small\">\n                        {{ 'Original source' | translate }}\n                      </p>\n                      <div class=\"d-flex gap-4 boxed-label\">\n                        <ng-container\n                          *ngTemplateOutlet=\"\n                            assetIcon;\n                            context: { $implicit: widgetAsset.deviceRef.value }\n                          \"\n                        ></ng-container>\n                        <span class=\"text-truncate\">\n                          <span\n                            class=\"text-truncate\"\n                            title=\"{{ widgetAsset.deviceRef.value.name }}\"\n                          >\n                            {{ widgetAsset.deviceRef.value.name }}\n                          </span>\n                          <small class=\"text-muted\">\n                            {{\n                              assetIdInfo | translate: { assetId: widgetAsset.deviceRef.value.id }\n                            }}\n                          </small>\n                        </span>\n                      </div>\n                    </div>\n                    <div\n                      class=\"col-md-1 col-xs-1 p-l-xs-0 p-l-sm-0 p-r-sm-0 p-r-xs-0 text-center p-t-16\"\n                    >\n                      <i\n                        class=\"icon-20\"\n                        [c8yIcon]=\"\n                          widgetAsset.selectedAsset !== undefined ? 'ok' : 'inactive-state'\n                        \"\n                        [title]=\"\n                          widgetAsset.selectedAsset !== undefined\n                            ? (assetSelectedLabel | translate)\n                            : (assetNotSelectedLabel | translate)\n                        \"\n                        [ngClass]=\"{\n                          'text-success': widgetAsset.selectedAsset !== undefined,\n                          'text-muted': widgetAsset.selectedAsset === undefined\n                        }\"\n                      ></i>\n                      @if (\n                        widgetAsset.selectedAsset === undefined && userClickedAcceptAllSuggested\n                      ) {\n                        <span\n                          class=\"c8y-icon-badge\"\n                          [tooltip]=\"\n                            'No suggestion available - widget will not be imported' | translate\n                          \"\n                          placement=\"right\"\n                        >\n                          <i\n                            class=\"status major stroked-icon\"\n                            c8yIcon=\"warning\"\n                          ></i>\n                        </span>\n                      }\n                    </div>\n                    <div class=\"col-md-4 col-xs-11 p-l-md-0\">\n                      <!-- Suggested/Selected source -->\n                      <p class=\"visible-sm visible-xs text-label-small m-t-16 m-b-8\">\n                        {{ 'Selected source' | translate }}\n                      </p>\n                      @if (\n                        widgetAsset.deviceRef.value.suggestedDevice && !widgetAsset.selectedAsset\n                      ) {\n                        <div\n                          class=\"d-flex gap-4 text-muted boxed-label\"\n                          [attr.data-label]=\"'Suggested' | translate\"\n                        >\n                          <ng-container\n                            *ngTemplateOutlet=\"\n                              assetIcon;\n                              context: { $implicit: widgetAsset.deviceRef.value.suggestedDevice }\n                            \"\n                          ></ng-container>\n                          <span class=\"text-truncate\">\n                            <span\n                              class=\"text-truncate\"\n                              title=\"{{ widgetAsset.deviceRef.value.suggestedDevice?.name }}\"\n                            >\n                              {{ widgetAsset.deviceRef.value.suggestedDevice?.name }}\n                            </span>\n                            <small class=\"text-muted\">\n                              {{\n                                assetIdInfo\n                                  | translate\n                                    : { assetId: widgetAsset.deviceRef.value.suggestedDevice?.id }\n                              }}\n                            </small>\n                          </span>\n                        </div>\n                      }\n                      @if (widgetAsset.selectedAsset) {\n                        <div class=\"d-flex gap-4 boxed-label\">\n                          <!-- selectedAsset icon -->\n                          <ng-container\n                            *ngTemplateOutlet=\"\n                              assetIcon;\n                              context: { $implicit: widgetAsset.selectedAsset }\n                            \"\n                          ></ng-container>\n                          <!-- selectedAsset Name and ID -->\n                          <span class=\"text-truncate\">\n                            <span\n                              class=\"text-truncate\"\n                              title=\"{{ widgetAsset.selectedAsset?.name }}\"\n                            >\n                              {{ widgetAsset.selectedAsset?.name }}\n                            </span>\n                            <small class=\"text-muted\">\n                              {{\n                                assetIdInfo | translate: { assetId: widgetAsset.selectedAsset.id }\n                              }}\n                            </small>\n                          </span>\n                        </div>\n                      }\n                      @if (\n                        !widgetAsset.deviceRef.value.suggestedDevice && !widgetAsset.selectedAsset\n                      ) {\n                        <div class=\"text-muted boxed-label\">\n                          <em>{{ 'No suggestion available' | translate }}</em>\n                        </div>\n                      }\n                    </div>\n                    <div class=\"col-md-3 p-0 flex-grow\">\n                      <div class=\"d-flex a-i-center j-c-end-md gap-8 boxed-label p-l-md-0 p-r-0\">\n                        <div class=\"input-group max-width-fit\">\n                          <div class=\"input-group-btn\">\n                            <button\n                              class=\"btn btn-default\"\n                              [attr.aria-label]=\"\n                                (widgetAsset.deviceRef.value.suggestedDevice\n                                  ? acceptSuggestedLabel\n                                  : noSuggestedAssetLabel\n                                ) | translate\n                              \"\n                              [tooltip]=\"\n                                (widgetAsset.deviceRef.value.suggestedDevice\n                                  ? acceptSuggestedLabel\n                                  : noSuggestedAssetLabel\n                                ) | translate\n                              \"\n                              placement=\"top\"\n                              container=\"body\"\n                              [ngClass]=\"{\n                                active:\n                                  widgetAsset.selectedAsset &&\n                                  widgetAsset.selectedAsset ===\n                                    widgetAsset.deviceRef.value.suggestedDevice\n                              }\"\n                              [disabled]=\"!widgetAsset.deviceRef.value.suggestedDevice\"\n                              (click)=\"\n                                widgetAsset.selectedAsset =\n                                  widgetAsset.deviceRef.value.suggestedDevice\n                              \"\n                            >\n                              <i [c8yIcon]=\"'check'\"></i>\n                            </button>\n                          </div>\n                          <div\n                            class=\"dropdown input-group-btn\"\n                            container=\"body\"\n                            dropdown\n                            [insideClick]=\"true\"\n                            #dropdown=\"bs-dropdown\"\n                          >\n                            <button\n                              class=\"btn btn-default dropdown-toggle c8y-dropdown\"\n                              [attr.aria-label]=\"'Select asset' | translate\"\n                              tooltip=\"{{ 'Select asset' | translate }}\"\n                              placement=\"top\"\n                              container=\"body\"\n                              type=\"button\"\n                              dropdownToggle\n                              [ngClass]=\"{\n                                active:\n                                  widgetAsset.selectedAsset &&\n                                  widgetAsset.selectedAsset !==\n                                    widgetAsset.deviceRef.value.suggestedDevice\n                              }\"\n                            >\n                              <i [c8yIcon]=\"'card-exchange'\"></i>\n                            </button>\n                            <div\n                              class=\"dropdown-menu dropdown-menu-right--md\"\n                              style=\"width: 300px\"\n                              *dropdownMenu\n                            >\n                              @if (dropdown.isOpen) {\n                                <c8y-asset-selector-miller\n                                  (onSelected)=\"\n                                    selectionChange($event, widgetAsset); dropdown.hide()\n                                  \"\n                                  [asset]=\"contextAsset\"\n                                  [config]=\"{\n                                    groupsSelectable: true,\n                                    showChildDevices: true,\n                                    showFilter: true,\n                                    showSelected: false,\n                                    columnHeaders: true,\n                                    singleColumn: true,\n                                    preventInitialSelect: true\n                                  }\"\n                                ></c8y-asset-selector-miller>\n                              }\n                            </div>\n                          </div>\n                          <div class=\"input-group-btn\">\n                            <button\n                              class=\"btn btn-default\"\n                              [attr.aria-label]=\"'Configure later' | translate\"\n                              tooltip=\"{{ 'Configure later' | translate }}\"\n                              placement=\"top\"\n                              container=\"body\"\n                              [ngClass]=\"{ active: widgetAsset.selectedAsset === null }\"\n                              (click)=\"widgetAsset.selectedAsset = null\"\n                            >\n                              <i [c8yIcon]=\"'link-off'\"></i>\n                            </button>\n                          </div>\n                        </div>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </c8y-li>\n          }\n        </c8y-list-group>\n      </div>\n    }\n  </div>\n  <div class=\"modal-footer\">\n    <button\n      class=\"btn btn-default\"\n      [title]=\"'Cancel' | translate\"\n      type=\"button\"\n      (click)=\"cancel()\"\n    >\n      {{ 'Cancel' | translate }}\n    </button>\n    <button\n      class=\"btn btn-primary\"\n      [title]=\"'Apply' | translate\"\n      type=\"button\"\n      (click)=\"apply()\"\n    >\n      {{ 'Apply' | translate }}\n    </button>\n  </div>\n</div>\n\n<ng-template\n  #assetIcon\n  let-device\n>\n  @if (device.c8y_IsDevice) {\n    <i\n      class=\"l-h-inherit\"\n      [c8yIcon]=\"'data-transfer'\"\n    ></i>\n  }\n  @if (device.c8y_IsDeviceGroup && !device.c8y_IsAsset) {\n    <i\n      class=\"l-h-inherit\"\n      [c8yIcon]=\"'c8y-group'\"\n    ></i>\n  }\n  @if (device.c8y_IsAsset) {\n    <i\n      class=\"l-h-inherit\"\n      [c8yIcon]=\"device.icon?.name || 'c8y-group'\"\n    ></i>\n  }\n</ng-template>\n" }]
        }], propDecorators: { assetsToAlign: [{
                type: Input
            }], contextAsset: [{
                type: Input
            }], notSupportedWidgets: [{
                type: Input
            }] } });

class ImportExportWidgetsService {
    constructor() {
        this.dynamicComponentService = inject(DynamicComponentService);
        this.injector = inject(Injector);
        this.alertService = inject(AlertService);
        this.dashboardDetailService = inject(DashboardDetailService);
        this.modal = inject(BsModalService);
    }
    /**
     * Export the dashboard to a JSON file. If the widget has an export method, it will be called to export
     * the widget's configuration.
     * @param dashboard Context dashboard to be exported
     */
    async export(dashboard) {
        const copiedDashboard = cloneDeep(dashboard);
        for (const [id, child] of Object.entries(copiedDashboard.children || {})) {
            try {
                const definition = await this.dynamicComponentService.getById(child.componentId);
                if (definition && typeof definition.data.export === 'function') {
                    const widgetData = definition.data;
                    const pluginInjector = definition.injector || this.injector;
                    await runInInjectionContext(pluginInjector, async () => {
                        const exportedConfig = await widgetData.export(child.config, this.dashboardDetailService.details, { pluginInjector, injector: this.injector });
                        copiedDashboard.children[id].config = exportedConfig;
                    });
                }
            }
            catch (error) {
                this.alertService.addServerFailure(error);
            }
        }
        const dateStr = new Date().toISOString().replace(/[:.]/g, '-');
        const filename = `dashboard_${dateStr}.json`;
        const blob = new Blob([JSON.stringify(copiedDashboard)], { type: 'application/json' });
        saveAs(blob, filename);
        this.alertService.success(gettext('Dashboard exported.'));
    }
    /**
     * Import the dashboard from a JSON file. If the widget has an import method, it will be called to import
     * widget's configuration.
     * @param file JSON file with context dashboard configuration
     */
    async import(file) {
        if (!file || file.length === 0) {
            return;
        }
        const dashboard = await file[0].readAsJson();
        const notSupportedWidgets = [];
        for (const [id, child] of Object.entries(dashboard.children)) {
            try {
                const definition = await this.dynamicComponentService.getById(child.componentId);
                if (definition && typeof definition.data.import === 'function') {
                    const widgetData = definition.data;
                    const pluginInjector = definition.injector || this.injector;
                    await runInInjectionContext(pluginInjector, async () => {
                        const importedConfig = await widgetData.import(child.config, this.dashboardDetailService.details, { pluginInjector, injector: this.injector });
                        dashboard.children[id].config = importedConfig;
                    });
                }
                else {
                    notSupportedWidgets.push(child.title);
                }
            }
            catch (error) {
                console.error(`Error importing widget with id ${id}:`, error);
            }
        }
        if (Object.entries(dashboard.children).length) {
            dashboard.children = await this.assignWidgetAssets(dashboard.children, notSupportedWidgets);
            if (!dashboard.children) {
                return;
            }
        }
        return JSON.stringify(dashboard, null, 2);
    }
    async assignWidgetAssets(widgets, notSupportedWidgets) {
        const assetsToAlign = this.findWidgetsWithSuggestedDevice(widgets);
        const contextAsset = this.dashboardDetailService.details.context;
        try {
            const modalRef = this.modal.show(AssignWidgetAssetModalComponent, {
                class: 'modal-lg',
                ariaDescribedby: 'modal-body',
                ariaLabelledBy: 'modal-title',
                ignoreBackdropClick: true,
                keyboard: false,
                initialState: { assetsToAlign, contextAsset, notSupportedWidgets }
            }).content;
            const result = await modalRef.result;
            return this.updateWidgets(widgets, result);
        }
        catch (_) {
            // cancel clicked
            return null;
        }
    }
    findWidgetsWithSuggestedDevice(widgets) {
        const results = [];
        const searchObject = (obj, path = []) => {
            if (!obj || typeof obj !== 'object')
                return;
            for (const [key, value] of Object.entries(obj)) {
                const currentPath = [...path, key];
                if (key === 'suggestedDevice') {
                    // Find the nearest device/__target reference
                    let deviceRef = null;
                    let devicePath = [];
                    // Search for __target or device in parent objects
                    let currentObj = obj;
                    const currentSearchPath = [...path];
                    while (currentObj && !deviceRef) {
                        if (currentObj.__target) {
                            deviceRef = currentObj.__target;
                            devicePath = [...currentSearchPath, '__target'];
                            break;
                        }
                        if (currentObj.device) {
                            deviceRef = currentObj.device;
                            devicePath = [...currentSearchPath, 'device'];
                            break;
                        }
                        // Move up one level
                        currentSearchPath.pop();
                        currentObj = this.getObjectByPath(widgets, currentSearchPath);
                    }
                    if (deviceRef) {
                        const widgetId = path[0]; // First element in path should be widget ID
                        results.push({
                            widgetId,
                            title: widgets[widgetId].title,
                            deviceRef: { path: devicePath, value: deviceRef }
                        });
                    }
                }
                else if (typeof value === 'object') {
                    searchObject(value, currentPath);
                }
            }
        };
        searchObject(widgets);
        return results;
    }
    getObjectByPath(obj, path) {
        return path.reduce((curr, key) => curr && curr[key], obj);
    }
    updateWidgets(originalWidgets, processedReferences) {
        const result = JSON.parse(JSON.stringify(originalWidgets));
        // remove widgets that were no aligned
        processedReferences.forEach(ref => {
            if (ref.deviceRef.value === undefined) {
                delete result[ref.widgetId];
                return;
            }
            // apply selected assets to widgets
            const obj = this.getObjectByPath(result, ref.deviceRef.path.slice(0, -1));
            const lastKey = ref.deviceRef.path[ref.deviceRef.path.length - 1];
            if (obj && lastKey) {
                obj[lastKey] = ref.deviceRef.value;
                delete obj[lastKey]?.suggestedDevice;
            }
        });
        return result;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ImportExportWidgetsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ImportExportWidgetsService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ImportExportWidgetsService, decorators: [{
            type: Injectable
        }] });

class DashboardJsonEditorComponent {
    constructor() {
        this.infoText = gettext("Import / Export allows copying dashboard configurations between assets using JSON files. You can export a dashboard's setup and import it to another asset.\n\nNote: Basic knowledge of dashboard JSON structure is required.");
        this.valueString = '';
        this.errors = signal([], ...(ngDevMode ? [{ debugName: "errors" }] : []));
        this.isExporting = false;
        this.isImporting = false;
        this.editorOptions = {
            hover: {
                above: false
            }
        };
        this.dashboardDetailService = inject(DashboardDetailService);
        this.jsonValidationService = inject(JsonValidationService);
        this.importExportWidgetsService = inject(ImportExportWidgetsService);
        this.valueChange = new Subject();
        this.destroy$ = new Subject();
        this.currentSchemas = null;
    }
    ngOnInit() {
        this.dashboard = this.dashboardDetailService.details.dashboard;
        this.editorTheme = this.getEditorTheme();
        this.valueString = JSON.stringify(this.dashboard, null, 2);
        this.valueChange.pipe(debounceTime(500), takeUntil(this.destroy$)).subscribe(() => {
            this.applyChanges();
            this.validate();
        });
    }
    ngOnDestroy() {
        this.destroy$.next();
        this.destroy$.complete();
    }
    dashboardJSONChange(value) {
        this.valueString = value;
        this.valueChange.next();
    }
    async assignSchema() {
        this.currentSchemas = await this.jsonValidationService.getJsonSchemas(this.dashboard);
        this.editorComponent.monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
            validate: true,
            schemas: this.currentSchemas,
            enableSchemaRequest: false,
            allowComments: false
        });
        this.validate();
    }
    async validate() {
        this.editorDirty = this.valueString !== JSON.stringify(this.dashboard, null, 2);
        try {
            const validationResult = await this.jsonValidationService.validateDashboard(this.valueString, this.currentSchemas);
            this.errors.set(validationResult);
        }
        catch (error) {
            console.error('Validation error:', error);
            this.errors.set([
                {
                    message: 'An error occurred during validation',
                    path: ''
                }
            ]);
        }
    }
    applyChanges() {
        try {
            const newDashboard = JSON.parse(this.valueString);
            this.dashboardDetailService.details.dashboard = newDashboard;
            this.dashboardDetailService.generalSettingsForm.patchValue(newDashboard);
            this.dashboardDetailService.appearanceSettingsForm.patchValue(newDashboard);
            this.dashboardDetailService.widgetsForm.patchValue(newDashboard);
            this.dashboardDetailService.previewChangedFn({
                classes: newDashboard.classes,
                widgetClasses: newDashboard.widgetClasses
            });
            this.dashboardDetailService.dashboardDetailsForm.markAsDirty();
            this.dashboardDetailService.saveButtonDisabled = false;
        }
        catch (error) {
            // disable save button if JSON is invalid and don't apply value to form
            this.dashboardDetailService.saveButtonDisabled = true;
        }
    }
    async export() {
        this.isExporting = true;
        try {
            await this.importExportWidgetsService.export(this.dashboard);
        }
        finally {
            this.isExporting = false;
        }
    }
    async import(file) {
        this.isImporting = true;
        try {
            if (file && file.length !== 0) {
                const importedValue = await this.importExportWidgetsService.import(file);
                if (importedValue) {
                    this.valueString = importedValue;
                    this.valueChange.next();
                }
            }
        }
        finally {
            this.isImporting = false;
        }
    }
    getEditorTheme() {
        const dashboardClasses = this.dashboardDetailService.appearanceSettingsForm.get('classes').value;
        if (dashboardClasses['dashboard-theme-dark']) {
            return 'dark';
        }
        else if (dashboardClasses['dashboard-theme-white']) {
            return 'light';
        }
        return null;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DashboardJsonEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: DashboardJsonEditorComponent, isStandalone: true, selector: "c8y-dashboard-json-editor", providers: [JsonValidationService, ImportExportWidgetsService], viewQueries: [{ propertyName: "editorComponent", first: true, predicate: EditorComponent, descendants: true }], ngImport: i0, template: "<div class=\"card-block\">\n<div\n  class=\"d-flex d-col\"\n  [ngStyle]=\"{ height: '410px' }\"\n>\n  <ng-container *ngIf=\"!isExporting && !isImporting; else loading\">\n    <div class=\"d-flex gap-16 flex-grow min-height-0\">\n      <div class=\"d-col col-md-8 col-xs-6\">\n        <label class=\"d-flex a-i-center\">{{ 'Dashboard configuration' | translate }} \n          <button \n            type=\"button\"\n            [attr.aria-label]=\"'Help' | translate\"\n            class=\"btn-help \" \n            placement=\"right\"\n            triggers=\"focus\"\n            containerClass=\"text-pre-wrap\"\n            [popover]=\"infoText | translate\"\n          ></button>\n        </label>\n        <c8y-editor\n          class=\"flex-grow min-height-0\"\n          [ngModel]=\"valueString\"\n          (ngModelChange)=\"dashboardJSONChange($event)\"\n          (editorInit)=\"assignSchema()\"\n          [theme]=\"editorTheme\"\n          monacoEditorMarkerValidator\n          [editorOptions]=\"editorOptions\"\n        ></c8y-editor>\n      </div>\n      <div class=\"col-md-4 d-col col-xs-6\">\n        <label translate>Validation</label>\n        <div\n          class=\"c8y-messages has-error m-b-24 inner-scroll flex-grow\"\n          *ngIf=\"errors().length > 0\"\n        >\n          <div\n            class=\"form-control-feedback-message\"\n            *ngFor=\"let error of errors()\"\n          >\n            <span\n              class=\"error-path\"\n              *ngIf=\"error.path\"\n            >\n              {{ error.path }}:\n            </span>\n            <span class=\"error-message\">{{ error.message }}</span>\n          </div>\n        </div>\n        <div\n          class=\"c8y-messages has-success m-b-24\"\n          *ngIf=\"errors().length === 0\"\n        >\n          <div\n            class=\"form-control-feedback-message\"\n          >\n            <span class=\"error-message\">{{ 'No errors found' | translate }}</span>\n          </div>\n        </div>\n        <div class=\"d-flex gap-16 a-i-center flex-no-shrink min-height-0 m-t-auto\">\n          <c8y-drop-area\n            class=\"drop-area-sm min-width-0\"\n            [icon]=\"'upload'\"\n            *ngIf=\"!isExporting\"\n            [accept]=\"'.json'\"\n            [maxAllowedFiles]=\"1\"\n            [message]=\"'Import JSON' | translate\"\n            (dropped)=\"import($event)\"\n            [loading]=\"isImporting\"\n          ></c8y-drop-area>\n          <button\n            class=\"btn btn-primary\"\n            (click)=\"export()\"\n            [disabled]=\"isExporting || isImporting\"\n            [ngClass]=\"{\n              'btn-pending': isExporting\n            }\"\n          >\n            {{'Export JSON' | translate}}\n          </button>\n        </div>\n      </div>\n    </div>\n  </ng-container>\n\n</div>\n\n<ng-template #loading>\n  <c8y-loading></c8y-loading>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i1.LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: CoreModule }, { kind: "component", type: i1.DropAreaComponent, selector: "c8y-drop-area", inputs: ["formControl", "title", "message", "icon", "loadingMessage", "forceHideList", "alwaysShow", "clickToOpen", "loading", "progress", "maxAllowedFiles", "files", "maxFileSizeInMegaBytes", "accept"], outputs: ["dropped"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i4$1.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "component", type: EditorComponent, selector: "c8y-editor", inputs: ["editorOptions", "theme"], outputs: ["editorInit"] }, { kind: "pipe", type: i1.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DashboardJsonEditorComponent, decorators: [{
            type: Component,
            args: [{ selector: 'c8y-dashboard-json-editor', standalone: true, imports: [
                        CommonModule,
                        FormsModule,
                        ReactiveFormsModule,
                        CoreModule,
                        PopoverModule,
                        EditorComponent
                    ], providers: [JsonValidationService, ImportExportWidgetsService], template: "<div class=\"card-block\">\n<div\n  class=\"d-flex d-col\"\n  [ngStyle]=\"{ height: '410px' }\"\n>\n  <ng-container *ngIf=\"!isExporting && !isImporting; else loading\">\n    <div class=\"d-flex gap-16 flex-grow min-height-0\">\n      <div class=\"d-col col-md-8 col-xs-6\">\n        <label class=\"d-flex a-i-center\">{{ 'Dashboard configuration' | translate }} \n          <button \n            type=\"button\"\n            [attr.aria-label]=\"'Help' | translate\"\n            class=\"btn-help \" \n            placement=\"right\"\n            triggers=\"focus\"\n            containerClass=\"text-pre-wrap\"\n            [popover]=\"infoText | translate\"\n          ></button>\n        </label>\n        <c8y-editor\n          class=\"flex-grow min-height-0\"\n          [ngModel]=\"valueString\"\n          (ngModelChange)=\"dashboardJSONChange($event)\"\n          (editorInit)=\"assignSchema()\"\n          [theme]=\"editorTheme\"\n          monacoEditorMarkerValidator\n          [editorOptions]=\"editorOptions\"\n        ></c8y-editor>\n      </div>\n      <div class=\"col-md-4 d-col col-xs-6\">\n        <label translate>Validation</label>\n        <div\n          class=\"c8y-messages has-error m-b-24 inner-scroll flex-grow\"\n          *ngIf=\"errors().length > 0\"\n        >\n          <div\n            class=\"form-control-feedback-message\"\n            *ngFor=\"let error of errors()\"\n          >\n            <span\n              class=\"error-path\"\n              *ngIf=\"error.path\"\n            >\n              {{ error.path }}:\n            </span>\n            <span class=\"error-message\">{{ error.message }}</span>\n          </div>\n        </div>\n        <div\n          class=\"c8y-messages has-success m-b-24\"\n          *ngIf=\"errors().length === 0\"\n        >\n          <div\n            class=\"form-control-feedback-message\"\n          >\n            <span class=\"error-message\">{{ 'No errors found' | translate }}</span>\n          </div>\n        </div>\n        <div class=\"d-flex gap-16 a-i-center flex-no-shrink min-height-0 m-t-auto\">\n          <c8y-drop-area\n            class=\"drop-area-sm min-width-0\"\n            [icon]=\"'upload'\"\n            *ngIf=\"!isExporting\"\n            [accept]=\"'.json'\"\n            [maxAllowedFiles]=\"1\"\n            [message]=\"'Import JSON' | translate\"\n            (dropped)=\"import($event)\"\n            [loading]=\"isImporting\"\n          ></c8y-drop-area>\n          <button\n            class=\"btn btn-primary\"\n            (click)=\"export()\"\n            [disabled]=\"isExporting || isImporting\"\n            [ngClass]=\"{\n              'btn-pending': isExporting\n            }\"\n          >\n            {{'Export JSON' | translate}}\n          </button>\n        </div>\n      </div>\n    </div>\n  </ng-container>\n\n</div>\n\n<ng-template #loading>\n  <c8y-loading></c8y-loading>\n</ng-template>\n" }]
        }], propDecorators: { editorComponent: [{
                type: ViewChild,
                args: [EditorComponent]
            }] } });

class DashboardDetailsAdvancedTabComponent {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DashboardDetailsAdvancedTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: DashboardDetailsAdvancedTabComponent, isStandalone: true, selector: "app-dashboard-details-advanced-tab", ngImport: i0, template: `<c8y-dashboard-json-editor></c8y-dashboard-json-editor>`, isInline: true, dependencies: [{ kind: "component", type: DashboardJsonEditorComponent, selector: "c8y-dashboard-json-editor" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DashboardDetailsAdvancedTabComponent, decorators: [{
            type: Component,
            args: [{
                    selector: 'app-dashboard-details-advanced-tab',
                    template: `<c8y-dashboard-json-editor></c8y-dashboard-json-editor>`,
                    standalone: true,
                    imports: [DashboardJsonEditorComponent]
                }]
        }] });

export { DashboardDetailsAdvancedTabComponent };
//# sourceMappingURL=c8y-ngx-components-dashboard-details-advanced-tab-dashboard-details-advanced-tab.component-L0Ns8oOr.mjs.map