@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
693 lines • 116 kB
JavaScript
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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