UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

693 lines 116 kB
import { __decorate, __metadata } from "tslib"; import { Component, HostBinding, HostListener, Inject, Input, Renderer2, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { ActionBarService, AlertService, CopyDashboardDisabledReason, DashboardChildChange, GainsightService, gettext, memoize, ModalService, Status, WidgetsDashboardComponent } from '@c8y/ngx-components'; import { TranslateService } from '@ngx-translate/core'; import { cloneDeep, every, findIndex, kebabCase, keyBy, omit, some } from 'lodash-es'; import { BsModalService } from 'ngx-bootstrap/modal'; import { CONTEXT_DASHBOARD_CONFIG, DASHBOARD_CHILDREN_STATE_NAME, PRODUCT_EXPERIENCE, WIDGET_HEADER_CLASSES } from './context-dashboard.model'; import { ContextDashboardService } from './context-dashboard.service'; import { DashboardDetailComponent } from './dashboard-detail.component'; import { WidgetConfigComponent } from './widget-config.component'; import { WidgetService } from './widget.service'; import { DashboardEditModeService } from './memento/dashboard-edit-mode.service'; import * as i0 from "@angular/core"; import * as i1 from "@angular/router"; import * as i2 from "./context-dashboard.service"; import * as i3 from "@c8y/ngx-components"; import * as i4 from "./widget.service"; import * as i5 from "ngx-bootstrap/modal"; import * as i6 from "@ngx-translate/core"; import * as i7 from "./memento/dashboard-edit-mode.service"; import * as i8 from "@angular/common"; import * as i9 from "ngx-bootstrap/popover"; import * as i10 from "./dashboard-detail.component"; /** * The context dashboard is a dashboard which resolves it data from the current context (device or group) * it is displayed on. It usually uses the route.data for it, but you can pass * a different managedObject to the [mo] input parameter to change that behavior. */ export class ContextDashboardComponent { get disabled() { return !this.hasPermissionToEditDashboard || !this.dashboardDetails?.isCollapsed; } constructor(route, router, contextDashboardService, alert, renderer, moduleConfig, widgetService, bsModal, gainsightService, actionBarService, translateService, modal, editModeService) { this.route = route; this.router = router; this.contextDashboardService = contextDashboardService; this.alert = alert; this.renderer = renderer; this.moduleConfig = moduleConfig; this.widgetService = widgetService; this.bsModal = bsModal; this.gainsightService = gainsightService; this.actionBarService = actionBarService; this.translateService = translateService; this.modal = modal; this.editModeService = editModeService; this.childrenClasses = ''; this.setTitle = false; this.defaultWidgets = []; this._canCopy = true; this.canDelete = true; this.isLoading = true; this.showContextHelpButton = true; /** * Hides dashboard availability selection. */ this.hideAvailability = false; this.class = ''; this.widgets = []; this.canCopy = true; this.dashboardTypeLabel = gettext('Dashboard for "{{ dashboardType }}"'); this.dashboardTypePopover = gettext(` The layout and configuration of this dashboard is used by all other assets of model type <strong>{{ dashboardType }}</strong>. Any changes made to this dashboard instance will be applied to all other instances.`); this.hasPermissionToEditDashboard = false; } ngOnInit() { if (!this.name) { this.loadContextDashboard(); return; } this.loadNamedDashboard(); } async beforeUnloadHandler($event) { const canDeactivate = await this.canDeactivate(true); if (!canDeactivate) { $event.returnValue = true; } } /** * Applies the current context to the widget * @param widget The widget to apply the context to. */ applyDeviceTarget(widget) { if (widget.config.device) { widget.config.device = { id: this.context.id, name: this.context.name }; } } /** * Removes the route listener. */ ngOnDestroy() { if (this.dataSub) { this.dataSub.unsubscribe(); } } /** * Guards component from unintended navigation away or closing tab without saving changes. * Navigation continues if true is returned, and navigation is cancelled if returned value is false. * @param omitConfirm Omits confirm calls inside method body (without this param, when method is used in * on 'beforeunload' event handler, error is thrown in console because confirm is blocked by browser anyway and returns false) */ async canDeactivate(omitConfirm = false) { const canDeactivate = (await this.dashboardDetails.canDeactivate(omitConfirm)) && (await this.widgetsDashboard.canDeactivate(omitConfirm)); // Needed in order to reset edit mode, otherwise when switching between dashboards, the edit mode is still enabled. if (canDeactivate) { await this.cancelEditMode(true); } return canDeactivate; } /** * Restores the dashboard widgets to the default widgets. */ async restore() { const mesg = gettext('You are about to reset the widgets of this dashboard. All changes to the dashboard widgets will get lost and cannot be recovered. Do you want to proceed?'); try { await this.modal.confirm(gettext('Reset widgets'), mesg, Status.WARNING, { ok: gettext('Reset`dashboard`'), cancel: gettext('Cancel') }); this.isLoading = true; this.mo.c8y_Dashboard.children = this.contextDashboardService.mapWidgets(this.defaultWidgets); this.mo.c8y_Dashboard.historyDescription = { changeType: 'reset' }; await this.contextDashboardService.update(this.mo); await this.onLoad(); } catch (error) { if (error) { this.alert.addServerFailure(error); } } finally { this.isLoading = false; } } /** * Method called on every widgets dimensions or position change but also for adding new widget (because adding * widget causes layout changes). It recognizes what type of change has been done and updates current widgets state * accordingly. * @param child Change object. */ positionOrWidgetDimensionChange(child) { const currentState = this.editModeService.getCurrentState()?.children || {}; const descriptionProp = child.children.length === Object.values(currentState).length ? 'arrangement' : 'added'; let changeName; let widgetsChanged; if (descriptionProp === 'added') { changeName = DASHBOARD_CHILDREN_STATE_NAME.added; const addedWidget = child.children.find(c => !currentState[c.data.id]); widgetsChanged = [addedWidget.data]; } else { changeName = DASHBOARD_CHILDREN_STATE_NAME.arrangement; widgetsChanged = child.children .map(c => { const lastStateChild = currentState[c.data.id]; if (c.x != lastStateChild._x || c.y != lastStateChild._y || c.width != lastStateChild._width || c.height != lastStateChild._height) { return c.data; } }) .filter(Boolean); } this.updateDashboardChildren(child, changeName, descriptionProp, widgetsChanged); } async revertChange(revertType) { let dashboardChildren; if (revertType === 'undo') { dashboardChildren = this.editModeService.undo().children; } else { dashboardChildren = this.editModeService.redo().children; } await this.setWidgets(dashboardChildren); } /** * Updates all dashboards children's. Useful for position changes on the dashboard. * @param child The child to change. * @param changeName Name of the change to indicate it on undo/redo button. * @param descriptionProp Property to add to dashboard change history. * @param widgetsChanged List of changed widgets. */ updateDashboardChildren(child, changeName, descriptionProp, widgetsChanged) { const { children } = child; const mappedChildren = keyBy(children.map(c => this.componentToWidget(c)), 'id'); this.setNewState({ name: changeName, children: mappedChildren }, descriptionProp, widgetsChanged); } /** * Copies the dashboard and current context to a clipboard. */ async copyDashboard() { const viewContext = this.route.parent.snapshot.data?.context; this.contextDashboardService.copyClipboard = { dashboardId: this.mo.id, dashboard: cloneDeep(this.mo.c8y_Dashboard), context: cloneDeep({ context: viewContext, contextData: this.context }) }; if (viewContext) { const ctx = viewContext.split('/').shift(); const msg = this.translateService.instant('Dashboard copied. Navigate to the desired {{ ctx }} and select "Paste dashboard"', { ctx }); this.alert.success(msg); } this.actionBarService.refresh(); } /** * Remove the complete dashboard and navigate away. */ async deleteDashboard() { await this.contextDashboardService.delete(this.mo); if (this.route.parent) { const route = this.route.parent.snapshot.url.map(segment => segment.path).join('/'); this.router.navigateByUrl(route); } await this.onDeleteGSEvent(); } get isDeviceTypeDashboard() { return !!this.dashboard?.deviceType; } /** * Edits a widget on the dashboard. * @param change The widget change event. */ async editWidget(change) { const { x, y, width, height } = change.source; const component = await this.widgetService.getWidgetDefinition(change.widget.name || change.widget.componentId); if (!component) { this.addWidget(); return; } await this.addWidget({ ...component, data: { ...component.data, ...change.widget, _x: x, _y: y, _width: width, _height: height } }); } /** * Adds a widget to the dashboard. * @param selected Define a selected component to switch to edit mode directly. */ async addWidget(selected) { const partialCloneSelected = selected ? { ...selected, data: cloneDeep(selected.data) } : selected; const activeContext = this.context.contextData ? this.context.contextData : this.context; const initialState = { mo: this.mo, context: this.context.c8y_Report ? {} : activeContext, selected: partialCloneSelected }; const modal = this.bsModal.show(WidgetConfigComponent, { class: 'modal-lg', ariaDescribedby: 'modal-body', ariaLabelledBy: 'modal-title', initialState, ignoreBackdropClick: true, keyboard: false }).content; try { const newWidget = await modal.result; if (!this.mo.c8y_Dashboard.children) { this.mo.c8y_Dashboard.children = {}; } newWidget.classes = this.mergeWidgetClasses(newWidget); await this.updateWidget(newWidget); // New state in edit mode is added only for widget update. When adding widget, layout change is always triggered, // so it's easier to set new state on layout change only if (selected) { const children = { ...this.editModeService.getCurrentState().children, [newWidget.id]: newWidget }; this.setNewState({ name: DASHBOARD_CHILDREN_STATE_NAME.config, children }, 'config', [ newWidget ]); } modal.close(); } catch (ex) { // intended empty } } async saveWidgetsToDashboard() { const currentState = this.editModeService.getCurrentState(); this.mo.c8y_Dashboard.children = currentState.children; this.mo.c8y_Dashboard.historyDescription = { changeType: 'update', widgetChanges: this.mapStateToHistoryDescription(currentState.changeHistory) }; await this.contextDashboardService.update(this.mo); this.editModeService.reset(); this.editModeService.init({ name: DASHBOARD_CHILDREN_STATE_NAME.initial, children: this.mo.c8y_Dashboard.children, changeHistory: {} }); this.isCopyDisabled = this.getDashboardCopyPermissionState(); } async cancelEditMode(onDeactivate = false) { const dashboardChildren = this.editModeService.reset(); if (this.dashboard) { this.dashboard.historyDescription = {}; } this.widgetsDashboard.editMode$.next(false); if (!onDeactivate) { // when setWidgets is called during navigation from device to device, navigation fails await this.setWidgets(dashboardChildren.children); } } /** * Updates a widget or adds a new one if it doesn't exist on * the dashboard. * @param widget The new widget */ async updateWidget(widget) { const index = findIndex(this.widgets, { id: widget.id }); const isNew = index === -1; const mappedWidget = await this.widgetService.mapLegacy(widget); if (isNew) { this.widgets.push(mappedWidget); } else { this.widgets.splice(index, 1, mappedWidget); } } /** * Removes a widget and rearranges the remaining ones * if necessary. * @param change The change event. */ async deleteWidget(change) { try { const { widget, source } = change; const removed = this.widgets.find(({ id }) => id === widget.id); this.widgets.splice(this.widgets.indexOf(removed), 1); const { dashboard } = source; dashboard.children = dashboard.children.filter(c => c.data.id !== widget.id); // using setTimeout to give the component the chance to remove it. const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)); await sleep(0); const child = new DashboardChildChange(source); child.collapseUpAll(); this.updateDashboardChildren(child, DASHBOARD_CHILDREN_STATE_NAME.removed, 'removed', [ widget ]); } catch (e) { this.alert.addServerFailure(e); } } /** * This is a workaround to ensure that the dragged-element * (which is attached to the body) has the right styling. */ addDashboardClassToBody() { this.class.split(' ').forEach(cssClass => { this.renderer.addClass(document.body, cssClass); }); } /** * This is a workaround to ensure that the dragged-element * (which is attached to the body) has the right styling. */ removeDashboardClassFromBody() { this.class.split(' ').forEach(cssClass => { this.renderer.removeClass(document.body, cssClass); }); } async onLoad(trackExperience) { this.hasPermissionToEditDashboard = this.dashboard && !this.dashboard.isTransient ? await this.contextDashboardService.canEditDashboard(this.mo) : false; this.canCopy = this._canCopy && !this.dashboard?.isTransient && (this.contextDashboardService.isDeviceDashboard(this.mo) || this.contextDashboardService.isDeviceType(this.mo) || this.contextDashboardService.isGroupDashboard(this.mo)); const dashboardChildren = cloneDeep(this.mo.c8y_Dashboard?.children); const dashboardClasses = { 'c8y-grid-dashboard': true, dashboard: true, ...this.dashboard?.classes }; this.isCopyDisabled = this.dashboard ? this.getDashboardCopyPermissionState() : { state: false, reason: null }; this.editModeService.init({ name: gettext('Initial state'), children: dashboardChildren, changeHistory: {} }); await this.setWidgets(dashboardChildren, trackExperience); this.class = Object.keys(dashboardClasses).join(' '); if (this.isReport) { this.addReportDashboardSettings(); } this.isLoading = false; if (!this.dashboard) { this.dashboardDetails.show(false); } else { await this.onLoadGSEvent(); } } async dashboardPreview(layoutChanges) { if (layoutChanges?.classes) { const dashboardClasses = { 'c8y-grid-dashboard': true, dashboard: true, ...layoutChanges.classes }; this.class = Object.keys(dashboardClasses).join(' '); } else if (this.dashboard) { this.dashboard.widgetClasses = { ...this.dashboard.widgetClasses, ...layoutChanges.widgetClasses }; } } async reloadDashboard(dashboardMo) { this.dashboard = dashboardMo.c8y_Dashboard; this.mo = dashboardMo; await this.onLoad(); } async setWidgets(dashboardChildren, trackExperience = false) { const isDeviceType = this.contextDashboardService.isDeviceType(this.mo); this.widgets = await Promise.all(Object.values(dashboardChildren || {}).map(widget => { widget.classes = this.mergeWidgetClasses(widget); if (isDeviceType) { this.applyDeviceTarget(widget); } if (trackExperience) { this.gainsightService.triggerEvent('loadWidget', { widgetName: widget.componentId || widget.name }); } return this.widgetService.mapLegacy(widget); })); } loadContextDashboard() { this.dataSub = this.route.data.subscribe(({ dashboard, isReport }) => { this.context = this.route.parent.snapshot.data.contextData || {}; this.title = this.context?.name; this.mo = dashboard; this.dashboard = this.mo.c8y_Dashboard; this.isReport = isReport || this.contextDashboardService.isReport(this.mo); if (this.isReport && this.dashboard && !this.dashboard?.name) { this.dashboard.name = this.title; } if (this.isReport && this.dashboard) { this.dashboard.c8y_IsNavigatorNode = this.context.c8y_IsNavigatorNode; } this.patchSensorPhoneDashboard(this.dashboard, this.mo.owner, this.context?.type); this.onLoad(true); }); } /** * To enable translation for widgets within the dashboard, the "translateWidgetTitle" flag must be enabled. * The property needs to be patched, as the "Sensor App" does not provide this setting. */ patchSensorPhoneDashboard(dashboard, owner, type) { if (type === 'c8y_SensorPhone' && owner?.includes('device_phone')) { dashboard.translateWidgetTitle = true; } } loadNamedDashboard() { this.dataSub = this.contextDashboardService .getNamedDashboardOrCreate(this.name, this.defaultWidgets, this.context) .subscribe(mo => { this.context = this.context || {}; this.mo = mo; this.dashboard = this.mo.c8y_Dashboard; this.onLoad(true); }); } mergeWidgetClasses(widget) { const hasHeaderClass = WIDGET_HEADER_CLASSES.find(el => widget.classes && widget.classes[el.class]); const widgetClasses = hasHeaderClass ? { ...widget.classes } : { ...this.dashboard.widgetClasses, ...widget.classes }; return { card: true, 'card-dashboard': true, [kebabCase(widget.componentId || widget.name)]: true, ...widgetClasses }; } componentToWidget(child) { return { ...omit(child.data, ['componentTransformConfigWithContext', 'transformConfigWithContext']), // remove legacy ...{ _x: child.x, _y: child.y, _width: child.width, _height: child.height } }; } addReportDashboardSettings() { this.setTitle = true; this.title = this.context?.name || gettext('New report'); this.breadcrumbSettings = { icon: 'th', label: 'Reports', path: 'reports' }; this.canDelete = false; } getDashboardCopyPermissionState() { if (!this.contextDashboardService.hasPermissionsToCopyDashboard()) { return { state: false, reason: CopyDashboardDisabledReason.PERMISSIONS }; } const allDashboardChildrenAreValid = every(this.mo.c8y_Dashboard.children, child => { const config = child.config || {}; const dataPoints = config.datapoints || []; return !((config.device && config.device.id !== this.context.id) || some(dataPoints, dataPoint => dataPoint.__target?.id !== this.context.id)); }); if (!allDashboardChildrenAreValid) { return { state: false, reason: CopyDashboardDisabledReason.WRONG_REFERENCE }; } return { state: true }; } setNewState(state, descriptionProp, widgetsChanged) { const description = this.getDescriptionForNewState(descriptionProp, widgetsChanged); this.editModeService.newState({ ...state, changeHistory: description }); } getDescriptionForNewState(descriptionProp, widgetsChanged) { const changeHistory = this.editModeService.getCurrentState().changeHistory; if (!changeHistory[descriptionProp]) { changeHistory[descriptionProp] = {}; } widgetsChanged.forEach(w => { if (descriptionProp === 'removed' && changeHistory.added?.[w.id]) { // if widget was added during current edit session and then removed, it should not be at description at all delete changeHistory.added[w.id]; if (changeHistory.config?.[w.id]) { delete changeHistory.config[w.id]; } if (changeHistory.arrangement?.[w.id]) { delete changeHistory?.arrangement[w.id]; } } else if ((descriptionProp === 'config' || descriptionProp === 'arrangement') && changeHistory.added?.[w.id]) { // if widget was added and then modified in current edit session, it should only be indicated as added } else { changeHistory[descriptionProp][w.id] = w; } }); return changeHistory; } mapStateToHistoryDescription(description) { const historyDescription = {}; for (const [key, value] of Object.entries(description)) { const widgets = Object.values(value); if (!widgets.length) { continue; } historyDescription[key] = Object.values(value).map(widget => widget.title); } return historyDescription; } async onDeleteGSEvent() { const parentName = await this.convertStringToHash(this.context?.name); const dashboardName = await this.convertStringToHash(this.dashboard?.name); this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE.DASHBOARD.EVENTS.DASHBOARDS, { component: PRODUCT_EXPERIENCE.DASHBOARD.COMPONENTS.DELETE_DASHBOARD, action: PRODUCT_EXPERIENCE.DASHBOARD.ACTIONS.DELETE, name: dashboardName, id: this.mo.id, nameId: `${dashboardName}_${this.mo.id}`, parentAssetId: this.context?.id ? this.context.id : 'noContext', parentAssetName: this.context?.id ? parentName : 'noContext', parentAssetType: this.context?.id ? this.context.type : 'noContext', parentAssetNameId: this.context?.id ? `${parentName}_${this.context.id}` : 'noContext', parentAssetNameDashboardName: this.context?.id ? `${parentName}_${dashboardName}` : 'noContext', parentAssetIdDashboardId: this.context?.id ? `${this.context.id}_${this.mo.id}` : 'noContext', parentAssetNameDashboardId: this.context?.id ? `${parentName}_${this.mo.id}` : 'noContext', parentAssetNameIdDashboardNameId: this.context?.id ? `${parentName}_${this.context.id}_${dashboardName}_${this.mo.id}` : 'noContext', dashboardType: this.dashboard.deviceType ? this.dashboard.deviceTypeValue : null, context: this.contextDashboardService.getContextForGS(this.context) }); } async onLoadGSEvent() { const parentName = await this.convertStringToHash(this.context?.name); const dashboardName = (await this.convertStringToHash(this.dashboard?.name)) ?? this.extractDefaultDashboardName(this.mo) ?? 'none'; this.gainsightService.triggerEvent(this.isReport ? PRODUCT_EXPERIENCE.DASHBOARD.EVENTS.REPORTS : PRODUCT_EXPERIENCE.DASHBOARD.EVENTS.DASHBOARDS, { component: PRODUCT_EXPERIENCE.DASHBOARD.COMPONENTS.DASHBOARD_VIEW, action: PRODUCT_EXPERIENCE.DASHBOARD.ACTIONS.LOAD, name: dashboardName, id: this.mo.id, nameId: `${dashboardName}_${this.mo.id}`, parentAssetId: this.context?.id ? this.context.id : 'noContext', parentAssetName: this.context?.id ? parentName : 'noContext', parentAssetType: this.context?.id ? this.context.type : 'noContext', parentAssetNameId: this.context?.id ? `${parentName}_${this.context.id}` : 'noContext', parentAssetNameDashboardName: this.context?.id ? `${parentName}_${dashboardName}` : 'noContext', parentAssetIdDashboardId: this.context?.id ? `${this.context.id}_${this.mo.id}` : 'noContext', parentAssetNameDashboardId: this.context?.id ? `${parentName}_${this.mo.id}` : 'noContext', parentAssetNameIdDashboardNameId: this.context?.id ? `${parentName}_${this.context.id}_${dashboardName}_${this.mo.id}` : 'noContext', dashboardType: this.dashboard.deviceType ? this.dashboard.deviceTypeValue : null, context: this.isReport ? PRODUCT_EXPERIENCE.DASHBOARD.CONTEXT.REPORT : this.contextDashboardService.getContextForGS(this.context) }); } extractDefaultDashboardName(obj) { const nameKey = Object.keys(obj).find(key => key.startsWith('c8y_Dashboard!name!')); if (nameKey) { return nameKey.split('!').pop(); } return null; } async convertStringToHash(str) { if (!str) { return null; } return (await this.gainsightService.shouldSendPiiData()) ? str : this.gainsightService.hashGroupName(str); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextDashboardComponent, deps: [{ token: i1.ActivatedRoute }, { token: i1.Router }, { token: i2.ContextDashboardService }, { token: i3.AlertService }, { token: i0.Renderer2 }, { token: CONTEXT_DASHBOARD_CONFIG }, { token: i4.WidgetService }, { token: i5.BsModalService }, { token: i3.GainsightService }, { token: i3.ActionBarService }, { token: i6.TranslateService }, { token: i3.ModalService }, { token: i7.DashboardEditModeService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: ContextDashboardComponent, selector: "c8y-context-dashboard", inputs: { name: "name", childrenClasses: "childrenClasses", context: "context", setTitle: "setTitle", defaultWidgets: "defaultWidgets", _canCopy: ["canCopy", "_canCopy"], canDelete: "canDelete", isLoading: "isLoading", breadcrumbSettings: "breadcrumbSettings", showContextHelpButton: "showContextHelpButton", translateWidgetTitle: "translateWidgetTitle", hideAvailability: "hideAvailability" }, host: { listeners: { "window:beforeunload": "beforeUnloadHandler($event)" }, properties: { "class": "this.class" }, styleAttribute: "\n display: block;\n ", classAttribute: "dashboard c8y-grid-dashboard" }, viewQueries: [{ propertyName: "dashboardDetails", first: true, predicate: DashboardDetailComponent, descendants: true, static: true }, { propertyName: "widgetsDashboard", first: true, predicate: WidgetsDashboardComponent, descendants: true, static: true }], ngImport: i0, template: "<c8y-title *ngIf=\"title\">\n {{ title }}\n</c8y-title>\n\n<c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"10000\"\n *ngIf=\"dashboard?.deviceType && dashboard.deviceTypeValue\"\n>\n <button\n class=\"btn-clean btn-link\"\n popoverTitle=\"{{ 'Dashboard template' | translate }}\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"dashboardTypePopoverRef\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n >\n <span class=\"tag tag--info text-12\">\n <span class=\"dashboard-template-marker\"></span>\n {{ dashboardTypeLabel | translate: { dashboardType: dashboard.deviceTypeValue } }}\n </span>\n </button>\n <ng-template #dashboardTypePopoverRef>\n <div\n [innerHTML]=\"dashboardTypePopover | translate: { dashboardType: dashboard.deviceTypeValue }\"\n ></div>\n </ng-template>\n</c8y-action-bar-item>\n\n<c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"-1\"\n *ngIf=\"defaultWidgets.length > 0\"\n>\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reset widgets' | translate }}\"\n type=\"button\"\n px-event=\"Reset dashboard\"\n (click)=\"restore()\"\n [disabled]=\"disabled || (widgetsDashboard?.editMode$ | async)\"\n data-cy=\"context-dashboard--button-reset-dashboard\"\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"reset\"\n ></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visible-lg-inline\">\n {{ 'Reset widgets' | translate }}\n </span>\n </button>\n</c8y-action-bar-item>\n\n<c8y-help\n src=\"/docs/cockpit/working-with-dashboards/#working-with-dashboards\"\n *ngIf=\"showContextHelpButton\"\n></c8y-help>\n\n<c8y-dashboard-detail\n class=\"d-contents\"\n [isReport]=\"isReport\"\n [dashboard]=\"dashboard\"\n [mo]=\"mo\"\n [context]=\"context\"\n [deviceType]=\"context?.type\"\n [hideAvailability]=\"hideAvailability\"\n (dashboardSaved)=\"reloadDashboard($event)\"\n (previewChanged)=\"dashboardPreview($event)\"\n></c8y-dashboard-detail>\n\n<c8y-widgets-dashboard\n [context]=\"context\"\n [contextDashboard]=\"dashboard\"\n [widgets]=\"widgets\"\n [isCopyDisabled]=\"isCopyDisabled\"\n [settings]=\"{\n isLoading: isLoading,\n isDisabled: disabled,\n canDelete: canDelete && !!dashboard,\n translateWidgetTitle: dashboard?.translateWidgetTitle ?? translateWidgetTitle,\n allowFullscreen: moduleConfig.allowFullscreen,\n title: setTitle ? dashboard?.name || title : undefined,\n widgetMargin: dashboard?.widgetMargin,\n canCopy: canCopy && !!dashboard,\n defaultWidth: dashboard?.columns >= 24 ? 8 : 4,\n columns: dashboard?.columns || 12\n }\"\n [breadcrumb]=\"breadcrumbSettings\"\n [editModeButtons]=\"{\n undoButtonDisabled: editModeService.undoButtonDisabled,\n changeToUndoName: editModeService.changeToUndoName,\n redoButtonDisabled: !editModeService.redoStackLastItem,\n changeToRedoName: editModeService.redoStackLastItem?.name\n }\"\n (onChangeDashboard)=\"positionOrWidgetDimensionChange($event)\"\n (onAddWidget)=\"addWidget()\"\n (onEditWidget)=\"editWidget($event)\"\n (onDeleteWidget)=\"deleteWidget($event)\"\n (onSaveDashboard)=\"saveWidgetsToDashboard()\"\n (onCancelDashboard)=\"cancelEditMode()\"\n (revertChange)=\"revertChange($event)\"\n (onChangeStart)=\"addDashboardClassToBody()\"\n (onChangeEnd)=\"removeDashboardClassFromBody()\"\n (onEditDashboard)=\"dashboardDetails.show(true)\"\n (onCopyDashboard)=\"copyDashboard()\"\n (onDeleteDashboard)=\"deleteDashboard()\"\n></c8y-widgets-dashboard>\n", dependencies: [{ kind: "component", type: i3.ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: i3.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i8.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: i3.WidgetsDashboardComponent, selector: "c8y-widgets-dashboard", inputs: ["widgets", "context", "contextDashboard", "settings", "isCopyDisabled", "breadcrumb", "editModeButtons"], outputs: ["onAddWidget", "onEditWidget", "onDeleteWidget", "onChangeDashboard", "onResize", "onEditDashboard", "onCopyDashboard", "onDeleteDashboard", "onChangeStart", "onChangeEnd", "onSaveDashboard", "onCancelDashboard", "revertChange"] }, { kind: "component", type: i3.HelpComponent, selector: "c8y-help", inputs: ["src", "isCollapsed", "priority", "icon"] }, { kind: "directive", type: i9.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: i10.DashboardDetailComponent, selector: "c8y-dashboard-detail", inputs: ["isReport", "deviceType", "context", "mo", "dashboard", "isNamedDashboard", "hideAvailability"], outputs: ["dashboardSaved", "previewChanged"] }, { kind: "pipe", type: i3.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i8.AsyncPipe, name: "async" }] }); } } __decorate([ memoize(), __metadata("design:type", Function), __metadata("design:paramtypes", [String]), __metadata("design:returntype", Promise) ], ContextDashboardComponent.prototype, "convertStringToHash", null); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextDashboardComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-context-dashboard', host: { style: ` display: block; `, class: 'dashboard c8y-grid-dashboard' }, template: "<c8y-title *ngIf=\"title\">\n {{ title }}\n</c8y-title>\n\n<c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"10000\"\n *ngIf=\"dashboard?.deviceType && dashboard.deviceTypeValue\"\n>\n <button\n class=\"btn-clean btn-link\"\n popoverTitle=\"{{ 'Dashboard template' | translate }}\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"dashboardTypePopoverRef\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n >\n <span class=\"tag tag--info text-12\">\n <span class=\"dashboard-template-marker\"></span>\n {{ dashboardTypeLabel | translate: { dashboardType: dashboard.deviceTypeValue } }}\n </span>\n </button>\n <ng-template #dashboardTypePopoverRef>\n <div\n [innerHTML]=\"dashboardTypePopover | translate: { dashboardType: dashboard.deviceTypeValue }\"\n ></div>\n </ng-template>\n</c8y-action-bar-item>\n\n<c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"-1\"\n *ngIf=\"defaultWidgets.length > 0\"\n>\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reset widgets' | translate }}\"\n type=\"button\"\n px-event=\"Reset dashboard\"\n (click)=\"restore()\"\n [disabled]=\"disabled || (widgetsDashboard?.editMode$ | async)\"\n data-cy=\"context-dashboard--button-reset-dashboard\"\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"reset\"\n ></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visible-lg-inline\">\n {{ 'Reset widgets' | translate }}\n </span>\n </button>\n</c8y-action-bar-item>\n\n<c8y-help\n src=\"/docs/cockpit/working-with-dashboards/#working-with-dashboards\"\n *ngIf=\"showContextHelpButton\"\n></c8y-help>\n\n<c8y-dashboard-detail\n class=\"d-contents\"\n [isReport]=\"isReport\"\n [dashboard]=\"dashboard\"\n [mo]=\"mo\"\n [context]=\"context\"\n [deviceType]=\"context?.type\"\n [hideAvailability]=\"hideAvailability\"\n (dashboardSaved)=\"reloadDashboard($event)\"\n (previewChanged)=\"dashboardPreview($event)\"\n></c8y-dashboard-detail>\n\n<c8y-widgets-dashboard\n [context]=\"context\"\n [contextDashboard]=\"dashboard\"\n [widgets]=\"widgets\"\n [isCopyDisabled]=\"isCopyDisabled\"\n [settings]=\"{\n isLoading: isLoading,\n isDisabled: disabled,\n canDelete: canDelete && !!dashboard,\n translateWidgetTitle: dashboard?.translateWidgetTitle ?? translateWidgetTitle,\n allowFullscreen: moduleConfig.allowFullscreen,\n title: setTitle ? dashboard?.name || title : undefined,\n widgetMargin: dashboard?.widgetMargin,\n canCopy: canCopy && !!dashboard,\n defaultWidth: dashboard?.columns >= 24 ? 8 : 4,\n columns: dashboard?.columns || 12\n }\"\n [breadcrumb]=\"breadcrumbSettings\"\n [editModeButtons]=\"{\n undoButtonDisabled: editModeService.undoButtonDisabled,\n changeToUndoName: editModeService.changeToUndoName,\n redoButtonDisabled: !editModeService.redoStackLastItem,\n changeToRedoName: editModeService.redoStackLastItem?.name\n }\"\n (onChangeDashboard)=\"positionOrWidgetDimensionChange($event)\"\n (onAddWidget)=\"addWidget()\"\n (onEditWidget)=\"editWidget($event)\"\n (onDeleteWidget)=\"deleteWidget($event)\"\n (onSaveDashboard)=\"saveWidgetsToDashboard()\"\n (onCancelDashboard)=\"cancelEditMode()\"\n (revertChange)=\"revertChange($event)\"\n (onChangeStart)=\"addDashboardClassToBody()\"\n (onChangeEnd)=\"removeDashboardClassFromBody()\"\n (onEditDashboard)=\"dashboardDetails.show(true)\"\n (onCopyDashboard)=\"copyDashboard()\"\n (onDeleteDashboard)=\"deleteDashboard()\"\n></c8y-widgets-dashboard>\n" }] }], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: i1.Router }, { type: i2.ContextDashboardService }, { type: i3.AlertService }, { type: i0.Renderer2 }, { type: undefined, decorators: [{ type: Inject, args: [CONTEXT_DASHBOARD_CONFIG] }] }, { type: i4.WidgetService }, { type: i5.BsModalService }, { type: i3.GainsightService }, { type: i3.ActionBarService }, { type: i6.TranslateService }, { type: i3.ModalService }, { type: i7.DashboardEditModeService }], propDecorators: { name: [{ type: Input }], childrenClasses: [{ type: Input }], context: [{ type: Input }], setTitle: [{ type: Input }], defaultWidgets: [{ type: Input }], _canCopy: [{ type: Input, args: ['canCopy'] }], canDelete: [{ type: Input }], isLoading: [{ type: Input }], breadcrumbSettings: [{ type: Input }], showContextHelpButton: [{ type: Input }], translateWidgetTitle: [{ type: Input }], hideAvailability: [{ type: Input }], class: [{ type: HostBinding, args: ['class'] }], dashboardDetails: [{ type: ViewChild, args: [DashboardDetailComponent, { static: true }] }], widgetsDashboard: [{ type: ViewChild, args: [WidgetsDashboardComponent, { static: true }] }], beforeUnloadHandler: [{ type: HostListener, args: ['window:beforeunload', ['$event']] }], convertStringToHash: [] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC1kYXNoYm9hcmQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vY29udGV4dC1kYXNoYm9hcmQvY29udGV4dC1kYXNoYm9hcmQuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vY29udGV4dC1kYXNoYm9hcmQvY29udGV4dC1kYXNoYm9hcmQuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sRUFDTCxTQUFTLEVBQ1QsV0FBVyxFQUNYLFlBQVksRUFDWixNQUFNLEVBQ04sS0FBSyxFQUdMLFNBQVMsRUFDVCxTQUFTLEVBQ1YsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6RCxPQUFPLEVBQ0wsZ0JBQWdCLEVBQ2hCLFlBQVksRUFFWiwyQkFBMkIsRUFFM0Isb0JBQW9CLEVBSXBCLGdCQUFnQixFQUNoQixPQUFPLEVBQ1AsT0FBTyxFQUNQLFlBQVksRUFFWixNQUFNLEVBR04seUJBQXlCLEVBQzFCLE1BQU0scUJBQXFCLENBQUM7QUFDN0IsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDdkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUN0RixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFckQsT0FBTyxFQUVMLHdCQUF3QixFQUl4Qiw2QkFBNkIsRUFHN0Isa0JBQWtCLEVBQ2xCLHFCQUFxQixFQUN0QixNQUFNLDJCQUEyQixDQUFDO0FBQ25DLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3RFLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQ3hFLE9B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