@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 142 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-datapoint-explorer-view.mjs","sources":["../../datapoint-explorer/view/configuration/naming-dictionary.ts","../../datapoint-explorer/view/configuration/name-generator.service.ts","../../datapoint-explorer/view/datapoint-explorer.model.ts","../../datapoint-explorer/view/configuration/workspace-configuration.service.ts","../../datapoint-explorer/view/datapoint-explorer.service.ts","../../datapoint-explorer/view/configuration/workspace-configuration.component.ts","../../datapoint-explorer/view/configuration/workspace-configuration.component.html","../../datapoint-explorer/view/create-new-report-modal/create-new-report-modal.component.ts","../../datapoint-explorer/view/create-new-report-modal/create-new-report-modal.component.html","../../datapoint-explorer/view/send-as-widget-to-dashboard-modal/send-as-widget-to-dashboard-modal.component.ts","../../datapoint-explorer/view/send-as-widget-to-dashboard-modal/send-as-widget-to-dashboard-modal.component.html","../../datapoint-explorer/view/send-as-widget-to-report-modal/send-as-widget-to-report-modal.component.ts","../../datapoint-explorer/view/send-as-widget-to-report-modal/send-as-widget-to-report-modal.component.html","../../datapoint-explorer/view/datapoint-explorer.component.ts","../../datapoint-explorer/view/datapoint-explorer.component.html","../../datapoint-explorer/view/c8y-ngx-components-datapoint-explorer-view.ts"],"sourcesContent":["export const adjectives = [\n 'caffeinated',\n 'sleepy',\n 'hungry',\n 'puzzled',\n 'overexcited',\n 'daydreaming',\n 'chocolate-loving',\n 'coffee-powered',\n 'cookie-craving',\n 'disco-ready',\n 'weekend-mode',\n 'pizza-powered',\n 'nap-seeking',\n 'wifi-hunting',\n 'battery-hungry',\n 'donut-powered',\n 'tea-sipping',\n 'keyboard-loving',\n 'screen-staring',\n 'mouse-chasing',\n 'code-dreaming',\n 'pixel-perfect',\n 'bug-finding',\n 'zoom-tired',\n 'meeting-dodging',\n 'deadline-racing',\n 'coffee-seeking',\n 'sandwich-craving',\n 'debug-ready',\n 'rest-needing'\n];\nexport const nouns = [\n 'sensor',\n 'robot',\n 'thermostat',\n 'gateway',\n 'dashboard',\n 'widget',\n 'gadget',\n 'button',\n 'antenna',\n 'beacon',\n 'adapter',\n 'gizmo',\n 'hub',\n 'switch',\n 'chip',\n 'controller',\n 'display',\n 'terminal',\n 'processor',\n 'transmitter',\n 'receiver',\n 'pod',\n 'device',\n 'module',\n 'relay',\n 'node',\n 'bridge',\n 'screen',\n 'router',\n 'box'\n];\n","import { Injectable } from '@angular/core';\nimport { adjectives, nouns } from './naming-dictionary';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class NameGeneratorService {\n generateName(): string {\n const getRandomElement = (arr: string[]) => arr[Math.floor(Math.random() * arr.length)];\n const randomAdjective = getRandomElement(adjectives);\n const randomNoun = getRandomElement(nouns);\n\n return `${randomAdjective}_${randomNoun}`;\n }\n}\n","import { DatapointsGraphWidgetConfig } from '@c8y/ngx-components/echart';\nimport { TimeContext } from '@c8y/ngx-components/time-context';\n\nexport interface Settings {\n hideWorkspaceConfig?: boolean;\n hideExportSelector?: boolean;\n hideWidgetActions?: boolean;\n timeContext?: TimeContext;\n defaultConfigurationId?: string;\n}\n\nexport interface DataExplorerUrlConfig {\n datapoints?: BaseDatapointConfig[];\n alarmsEventsConfigs?: BaseAlarmEventConfig[];\n dateFrom?: Date | string | null;\n dateTo?: Date | string | null;\n\n // Allow any additional fields so it's forward-compatible\n [key: string]: any;\n}\n\nexport interface BaseDatapointConfig {\n fragment: string;\n series: string;\n __target?: {\n id?: string;\n [key: string]: any;\n };\n __template?: string;\n\n [key: string]: any;\n}\n\nexport interface BaseAlarmEventConfig {\n timelineType: string;\n label: string;\n\n filters: {\n type: string;\n [key: string]: any;\n };\n\n __target?: {\n id?: string;\n [key: string]: any;\n };\n [key: string]: any;\n}\n\nexport const DATA_EXPLORER_BASE_CONFIG: DatapointsGraphWidgetConfig = {\n datapoints: [],\n alarmsEventsConfigs: [],\n dateFrom: null,\n dateTo: null,\n interval: 'hours',\n aggregation: null,\n realtime: false,\n isAutoRefreshEnabled: false,\n refreshInterval: 0,\n displayMarkedLine: true,\n displayMarkedPoint: true,\n mergeMatchingDatapoints: false,\n forceMergeDatapoints: false,\n showLabelAndUnit: true,\n showSlider: true,\n displayDateSelection: false,\n yAxisSplitLines: false,\n xAxisSplitLines: false,\n numberOfDecimalPlaces: 2\n};\n\nexport const REVERSE_KEY_MAP = {\n d: 'datapoints',\n a: 'alarmsEventsConfigs',\n f: 'fragment',\n s: 'series',\n t: '__target',\n m: '__template',\n l: 'label',\n r: 'filters',\n y: 'timelineType',\n df: 'dateFrom',\n dt: 'dateTo',\n ac: '__active',\n c: 'color',\n i: 'id',\n tp: 'type'\n};\n\nexport const KEY_MAP = Object.fromEntries(\n Object.entries(REVERSE_KEY_MAP).map(([shortKey, longKey]) => [longKey, shortKey])\n);\n","import { inject, Injectable, signal, WritableSignal } from '@angular/core';\nimport { WorkspaceConfiguration } from './workspace-configuration.model';\nimport { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string';\nimport {\n DATA_EXPLORER_BASE_CONFIG,\n DataExplorerUrlConfig,\n KEY_MAP,\n REVERSE_KEY_MAP\n} from '../datapoint-explorer.model';\nimport { DatapointsGraphWidgetConfig } from '@c8y/ngx-components/echart';\nimport { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\nimport { IIdentified } from '@c8y/client';\nimport { AlertService } from '@c8y/ngx-components';\nimport { gettext } from '@c8y/ngx-components/gettext';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class WorkspaceConfigurationService {\n private readonly baseKey = 'c8y-configs';\n private readonly baseDefaultKey = 'c8y-default-config-id';\n contextIdSignal: WritableSignal<number | string | null> = signal(null);\n\n private readonly alertService = inject(AlertService);\n\n get LOCAL_STORAGE_KEY(): string {\n const id = this.contextIdSignal();\n return id !== null ? `${this.baseKey}-${id}` : this.baseKey;\n }\n\n get LOCAL_STORAGE_DEFAULT_ID_KEY(): string {\n const id = this.contextIdSignal();\n return id !== null ? `${this.baseDefaultKey}-${id}` : this.baseDefaultKey;\n }\n\n /**\n * Generates a full datapoint explorer link from a bare config\n */\n generateExplorerLink(\n config: Partial<DatapointsGraphWidgetConfig> | DataExplorerUrlConfig,\n label: string,\n id: string\n ): string {\n const diffed = this.removeDefaults(config);\n const minified = this.minifyKeys(diffed);\n const compressed = compressToEncodedURIComponent(JSON.stringify(minified));\n const url = `/datapointexplorer-v2?id=${id}&label=${encodeURIComponent(label)}&config=${compressed}`;\n\n return url;\n }\n\n /** Load workspace configs from localStorage */\n getConfigurations(): WorkspaceConfiguration[] {\n const configurations = localStorage.getItem(this.LOCAL_STORAGE_KEY);\n if (configurations) {\n const parsedConfigs: WorkspaceConfiguration[] = JSON.parse(configurations);\n return parsedConfigs.map(workspaceConfig => {\n if (typeof workspaceConfig.config === 'string') {\n return {\n ...workspaceConfig,\n config: this.decodeConfig(workspaceConfig.config)\n };\n }\n return workspaceConfig;\n });\n }\n return [];\n }\n\n getDefaultConfigurationId(): string | null {\n return localStorage.getItem(this.LOCAL_STORAGE_DEFAULT_ID_KEY);\n }\n\n /** Save workspace configs in localStorage */\n saveConfigurations(configurations: WorkspaceConfiguration[], id: string): void {\n // Before saving, we need to clean up the config objects to remove any unnecessary properties in the __target object\n configurations = configurations.map(workspace => ({\n ...workspace,\n config: this.cleanUpTargetObject(workspace.config)\n }));\n localStorage.setItem(this.LOCAL_STORAGE_KEY, JSON.stringify(configurations));\n localStorage.setItem(this.LOCAL_STORAGE_DEFAULT_ID_KEY, id);\n }\n\n /**\n *\n * @param urlConfig - configuration from the URL, either compressed string or already decoded object\n * @returns\n */\n getConfigurationFromUrl(\n urlConfig: DatapointsGraphWidgetConfig | string\n ): DatapointsGraphWidgetConfig {\n if (typeof urlConfig === 'string') {\n const decodedConfig = this.decodeConfig(urlConfig);\n const expandedConfig = this.expandKeys(decodedConfig);\n return expandedConfig;\n }\n\n return this.expandKeys(urlConfig);\n }\n\n /**\n * Encode a config for the URL:\n * - Cleanup the __target objects to contain only name and id\n * - Remove default values (diff from base)\n * - Minify keys\n * - Compress with lz-string\n */\n encodeConfig(config: DatapointsGraphWidgetConfig): string {\n const normalized = this.cleanUpTargetObject(config);\n const diffed = this.removeDefaults(normalized);\n const minified = this.minifyKeys(diffed);\n return compressToEncodedURIComponent(JSON.stringify(minified));\n }\n\n /**\n * Decode a config from the URL:\n * - Decompress\n * - Expand keys\n * - Merge with base config\n */\n decodeConfig(urlConfig: string): Partial<DatapointsGraphWidgetConfig> {\n if (!urlConfig) return null;\n try {\n const decompressed = decompressFromEncodedURIComponent(urlConfig);\n if (!decompressed) return null;\n const parsed = JSON.parse(decompressed);\n const expanded = this.expandKeys(parsed) as Partial<DatapointsGraphWidgetConfig>;\n return { ...DATA_EXPLORER_BASE_CONFIG, ...expanded };\n } catch {\n this.alertService.danger(\n gettext('The decoded configuration is invalid and could not be loaded.')\n );\n }\n }\n\n /** Minify keys recursively using KEY_MAP */\n private minifyKeys(\n config: Partial<DatapointsGraphWidgetConfig>\n ): Record<keyof typeof KEY_MAP, unknown> | unknown {\n if (Array.isArray(config)) return config.map(object => this.minifyKeys(object));\n if (config !== Object(config)) return config;\n\n return Object.fromEntries(\n Object.entries(config as Record<string, unknown>).map(([originalKey, originalValue]) => [\n KEY_MAP[originalKey] || originalKey,\n this.minifyKeys(originalValue)\n ])\n );\n }\n\n /** Expand keys recursively using REVERSE_KEY_MAP */\n private expandKeys(\n config: Partial<DatapointsGraphWidgetConfig>\n ): Record<keyof typeof REVERSE_KEY_MAP, unknown> | unknown {\n if (Array.isArray(config)) return config.map(o => this.expandKeys(o));\n if (config !== Object(config)) return config;\n\n return Object.fromEntries(\n Object.entries(config as Record<string, unknown>).map(([originalKey, originalValue]) => [\n REVERSE_KEY_MAP[originalKey] || originalKey,\n this.expandKeys(originalValue)\n ])\n );\n }\n\n /**\n * Remove properties from `config` that match `base` so only changed values remain.\n */\n private removeDefaults(\n config: DatapointsGraphWidgetConfig | DataExplorerUrlConfig\n ): DatapointsGraphWidgetConfig {\n const result = {};\n\n for (const key in config) {\n if (!(key in DATA_EXPLORER_BASE_CONFIG) || DATA_EXPLORER_BASE_CONFIG[key] !== config[key]) {\n result[key] = config[key];\n }\n }\n\n return result;\n }\n\n /**\n * Cleans up __target objects to only keep id and name and remove the rest.\n */\n private cleanUpTargetObject(config: DatapointsGraphWidgetConfig): DatapointsGraphWidgetConfig {\n const configCopy: any = { ...config };\n\n // Cleanup the datapoints array\n if (Array.isArray(configCopy.datapoints)) {\n configCopy.datapoints = configCopy.datapoints.map((dp: KPIDetails) => {\n if (dp && typeof dp === 'object' && dp.__target && typeof dp.__target === 'object') {\n const { id, name } = dp.__target;\n const target: IIdentified = {};\n if (id !== undefined) target.id = id;\n if (name !== undefined) target.name = name;\n return { ...dp, __target: target };\n }\n return dp;\n });\n }\n\n // Cleanup the alarmEventsConfig array\n if (Array.isArray(configCopy.alarmsEventsConfigs)) {\n configCopy.alarmsEventsConfigs = configCopy.alarmsEventsConfigs.map((a: KPIDetails) => {\n if (a && typeof a === 'object' && a.__target && typeof a.__target === 'object') {\n const { id, name } = a.__target;\n const target: IIdentified = {};\n if (id !== undefined) target.id = id;\n if (name !== undefined) target.name = name;\n return { ...a, __target: target };\n }\n return a;\n });\n }\n\n return configCopy;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { IManagedObject, InventoryService } from '@c8y/client';\nimport { DatapointSyncService, MOChunkLoaderService } from '@c8y/ngx-components';\nimport {\n ContextDashboardService,\n ContextDashboardType\n} from '@c8y/ngx-components/context-dashboard';\nimport { firstValueFrom } from 'rxjs';\nimport { WorkspaceConfigurationService } from './configuration/workspace-configuration.service';\nimport { Router } from '@angular/router';\nimport { DatapointsGraphKPIDetails, DatapointsGraphWidgetConfig } from '@c8y/ngx-components/echart';\nimport { DataExplorerUrlConfig } from './datapoint-explorer.model';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class DataExplorerService {\n private readonly inventory = inject(InventoryService);\n private readonly contextDashboardService = inject(ContextDashboardService);\n private readonly workspaceConfigurationService = inject(WorkspaceConfigurationService);\n private readonly moChunkLoader = inject(MOChunkLoaderService);\n private readonly datapointSyncService = inject(DatapointSyncService);\n private readonly router = inject(Router);\n private readonly maxNumberOfManagedObjectsPerRequest = 50;\n\n async fetchReportDashboard(reportId: string): Promise<IManagedObject> {\n return (\n await this.inventory.list({\n pageSize: 1,\n query: `has('c8y_Dashboard!name!report_${reportId}')`\n })\n ).data[0];\n }\n\n async fetchContextDashboard(\n dashboardId: string,\n contextAsset: IManagedObject\n ): Promise<IManagedObject> {\n const context = contextAsset.c8y_isDevice\n ? ContextDashboardType.Device\n : ContextDashboardType.Group;\n return firstValueFrom(this.contextDashboardService.getDashboard$(dashboardId, [context]));\n }\n\n async loadManagedObjectsInChunks(uniqIds: string[]) {\n const { results, errors } = await this.moChunkLoader.processInChunks(\n uniqIds,\n this.maxNumberOfManagedObjectsPerRequest,\n ids => this.loadAChunkOfManagedObjects(ids)\n );\n\n return { result: results, errors };\n }\n\n /**\n * Navigate to datapoint explorer with given config.\n * The goal of this method is to navigate to the data explorer with a provided config from any other application.\n * @param config Configuration to use\n * @param label Label to be displayed for the configuration\n * @param id ID for the configuration\n */\n navigateToDataExplorer(config: DataExplorerUrlConfig, label: string, id: string): void {\n const url = this.getUrlForConfig(config, label, id);\n this.router.navigateByUrl(url);\n }\n\n /**\n * Generate a URL for the datapoint explorer with the given config.\n * The goal of this method is to generate a shareable link to the data explorer.\n * @param config Configuration to use\n * @param label Label to be displayed for the configuration\n * @param id ID for the configuration\n * @returns The generated URL\n */\n getUrlForConfig(config: DataExplorerUrlConfig, label: string, id: string): string {\n return this.workspaceConfigurationService.generateExplorerLink(\n config as Partial<DatapointsGraphWidgetConfig>,\n label,\n id\n );\n }\n\n processAlarmEventConfigs(config: DatapointsGraphWidgetConfig): void {\n const firstTarget = config.alarmsEventsConfigs.find(ae => ae.__target)?.__target;\n\n config.alarmsEventsConfigs = config.alarmsEventsConfigs.map((ae, index) => {\n if (ae.__active === undefined) ae.__active = true;\n if (!ae.__target && firstTarget) ae.__target = firstTarget;\n if (!ae.color) ae.color = this.generateColor(index);\n return ae;\n });\n }\n\n processDatapoints(config: DatapointsGraphWidgetConfig): void {\n const firstTarget = config.datapoints.find(dp => dp.__target)?.__target;\n\n config.datapoints = config.datapoints.map((dp, index) => {\n // Default __active\n if (dp.__active === undefined) dp.__active = true;\n\n if (!dp.__target && firstTarget) dp.__target = firstTarget;\n\n if (!dp.color && !dp.__template) dp.color = this.generateColor(index);\n\n if (!dp.label) dp.label = `${dp.fragment} -> ${dp.series}`;\n\n return dp;\n });\n }\n\n /**\n * Generates a color from a fixed palette based on the index.\n * Used to assign colors to alarm/event configs in the UI.\n */\n generateColor(index: number): string {\n // Simple palette, can expand\n const palette = ['#c87d33', '#8c145f', '#8cd7fd', '#59a036', '#fb00ff', '#8d4c22', '#fbb2d7'];\n return palette[index % palette.length];\n }\n\n async loadAndAssignManagedObjects(\n config: DatapointsGraphWidgetConfig,\n uniqueIds: string[]\n ): Promise<void> {\n const managedObjectsResult = await this.loadManagedObjectsInChunks([...uniqueIds]);\n const managedObjects = managedObjectsResult.result;\n const errors = managedObjectsResult.errors;\n\n config.datapoints = this.datapointSyncService.assignUpdatedValues(\n config.datapoints,\n managedObjects,\n errors\n ) as DatapointsGraphKPIDetails[];\n }\n\n private async loadAChunkOfManagedObjects(uniqIds: string[]) {\n return this.moChunkLoader.loadAChunkOfManagedObjectsBase(\n uniqIds,\n this.inventory,\n this.maxNumberOfManagedObjectsPerRequest,\n id => this.moChunkLoader.getStatusDetails(id)\n );\n }\n}\n","import { A11yModule } from '@angular/cdk/a11y';\nimport {\n Component,\n EventEmitter,\n inject,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n SimpleChanges\n} from '@angular/core';\nimport { FormArray, FormBuilder, FormGroup, FormsModule } from '@angular/forms';\nimport { ActivatedRoute, NavigationStart, Router } from '@angular/router';\nimport {\n ClipboardService,\n ContextRouteService,\n CoreModule,\n DatapointSyncService,\n ViewContext\n} from '@c8y/ngx-components';\nimport { DatapointsGraphWidgetConfig } from '@c8y/ngx-components/echart';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { BsDropdownModule } from 'ngx-bootstrap/dropdown';\nimport { TooltipModule } from 'ngx-bootstrap/tooltip';\nimport { NameGeneratorService } from './name-generator.service';\nimport { WorkspaceConfiguration } from './workspace-configuration.model';\nimport { WorkspaceConfigurationService } from './workspace-configuration.service';\nimport { DataExplorerService } from '../datapoint-explorer.service';\n\n@Component({\n selector: 'c8y-workspace-config',\n templateUrl: './workspace-configuration.component.html',\n standalone: true,\n imports: [CoreModule, BsDropdownModule, FormsModule, A11yModule, TooltipModule]\n})\nexport class WorkspaceConfigComponent implements OnInit, OnChanges, OnDestroy {\n @Input() updatedConfig: DatapointsGraphWidgetConfig;\n @Input() silent = false;\n @Input() defaultConfigurationId: string;\n @Output() onConfigurationChange = new EventEmitter<DatapointsGraphWidgetConfig>();\n\n currentConfiguration: WorkspaceConfiguration;\n configurations: WorkspaceConfiguration[] = [];\n configurationsFormGroup: FormGroup;\n navigationSubscription = null;\n shouldCleanParams = false;\n activeConfigTooltip = gettext('Active configuration cannot be removed');\n removeConfigTooltip = gettext('Remove configuration');\n\n private readonly formBuilder = inject(FormBuilder);\n private readonly workspaceConfigurationService = inject(WorkspaceConfigurationService);\n private readonly contextRouteService = inject(ContextRouteService);\n private readonly clipboardService = inject(ClipboardService);\n private readonly activatedRoute = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly nameGeneratorService = inject(NameGeneratorService);\n private readonly datapointSyncService = inject(DatapointSyncService);\n private readonly dataExplorerService = inject(DataExplorerService);\n\n ngOnInit(): void {\n this.initializeContextSourceId();\n this.initializeConfigurations();\n this.initWorkspaceForm();\n this.subscribeToRouterEvents();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.updatedConfig && !changes.updatedConfig.firstChange) {\n this.updateConfigurations();\n }\n }\n\n ngOnDestroy(): void {\n if (this.shouldCleanParams) {\n this.router.navigate([], {\n queryParams: {\n config: null,\n id: null,\n label: null\n },\n queryParamsHandling: 'merge'\n });\n }\n\n this.navigationSubscription?.unsubscribe();\n }\n\n addConfig(duplicatedConfig?: DatapointsGraphWidgetConfig, id?: string, label?: string): void {\n const name = this.nameGeneratorService.generateName();\n const workspace: WorkspaceConfiguration = {\n id: id || new Date().toISOString(),\n label: label || name,\n config: { datapoints: [], alarmsEventsConfigs: [] }\n };\n if (duplicatedConfig) {\n workspace.config = duplicatedConfig;\n }\n this.configurations = [workspace, ...this.configurations];\n this.initWorkspaceForm();\n this.changeConfiguration(true, workspace);\n }\n\n changeConfiguration(selected: boolean, configuration: WorkspaceConfiguration): void {\n if (!selected) {\n return;\n }\n\n if (this.currentConfiguration?.id === configuration.id) {\n return;\n }\n\n this.currentConfiguration = configuration;\n localStorage.setItem(\n this.workspaceConfigurationService.LOCAL_STORAGE_DEFAULT_ID_KEY,\n this.currentConfiguration.id\n );\n this.onConfigurationChange.emit(configuration.config);\n }\n\n updateConfigurationLabel(configuration: WorkspaceConfiguration): void {\n this.configurations = this.configurations.map(c =>\n c.id === configuration.id ? configuration : c\n );\n if (this.currentConfiguration.id === configuration.id) {\n this.currentConfiguration = configuration;\n }\n this.workspaceConfigurationService.saveConfigurations(\n this.configurations,\n this.currentConfiguration?.id || ''\n );\n }\n\n deleteConfiguration(configuration: WorkspaceConfiguration): void {\n this.configurations = this.configurations.filter(c => c.id !== configuration.id);\n this.initWorkspaceForm();\n this.workspaceConfigurationService.saveConfigurations(\n this.configurations,\n this.currentConfiguration?.id || ''\n );\n }\n\n clearAll(): void {\n this.configurations = [this.currentConfiguration];\n this.initWorkspaceForm();\n this.workspaceConfigurationService.saveConfigurations(\n this.configurations,\n this.currentConfiguration?.id || ''\n );\n }\n\n async shareConfig(configuration: WorkspaceConfiguration): Promise<void> {\n await this.clipboardService.writeText(JSON.stringify(configuration.config));\n }\n\n private async addConfigFromUrl(queryParams: Partial<WorkspaceConfiguration>): Promise<void> {\n const id = queryParams.id || new Date().toISOString();\n const label = queryParams.label || this.nameGeneratorService.generateName();\n const config = this.workspaceConfigurationService.getConfigurationFromUrl(\n queryParams.config || {}\n );\n\n this.addConfig(config, id, label);\n\n if (config.datapoints?.length) {\n this.dataExplorerService.processDatapoints(config);\n }\n\n const uniqueIds = this.datapointSyncService.getManagedObjectIds(config.datapoints || []);\n if (uniqueIds.length) {\n await this.dataExplorerService.loadAndAssignManagedObjects(config, uniqueIds);\n }\n\n if (config.alarmsEventsConfigs?.length) {\n this.dataExplorerService.processAlarmEventConfigs(config);\n }\n\n this.onConfigurationChange.emit(config);\n }\n\n private updateConfigurations(): void {\n const config = { ...this.updatedConfig };\n this.currentConfiguration.config = config;\n\n this.configurations = this.configurations.map(currentConfig =>\n currentConfig.id === this.currentConfiguration.id ? this.currentConfiguration : currentConfig\n );\n const queryParams = {\n id: this.currentConfiguration.id,\n label: this.currentConfiguration.label,\n config: this.workspaceConfigurationService.encodeConfig(config)\n };\n\n this.workspaceConfigurationService.saveConfigurations(\n this.configurations,\n this.currentConfiguration.id\n );\n const control = <FormArray>this.configurationsFormGroup.controls['configurations'];\n const index = this.configurations.findIndex(c => c.id === this.currentConfiguration.id);\n if (index !== -1) {\n control.at(index).patchValue({\n label: this.currentConfiguration.label,\n config: this.currentConfiguration.config,\n id: this.currentConfiguration.id\n });\n }\n\n this.router.navigate([], {\n queryParams,\n queryParamsHandling: 'merge'\n });\n }\n\n private initializeConfigurations(): void {\n const configurations = this.workspaceConfigurationService.getConfigurations();\n const defaultId =\n this.defaultConfigurationId ?? this.workspaceConfigurationService.getDefaultConfigurationId();\n const queryParams = this.router.parseUrl(this.router.url).queryParams;\n\n if (configurations.length) {\n this.configurations = configurations;\n this.currentConfiguration =\n this.configurations.find(c => c.id === defaultId) || this.configurations[0];\n\n this.onConfigurationChange.emit(this.currentConfiguration.config);\n if (queryParams?.id && !this.configurations.find(c => c.id === queryParams.id)) {\n this.addConfigFromUrl(queryParams);\n } else if (\n queryParams?.id &&\n this.configurations.find(c => c.id === queryParams.id) &&\n this.currentConfiguration.id !== queryParams.id\n ) {\n this.currentConfiguration = this.configurations.find(c => c.id === queryParams.id);\n this.changeConfiguration(true, this.currentConfiguration);\n }\n }\n\n if (!this.currentConfiguration) {\n if (Object.keys(queryParams).length === 0) {\n this.addConfig();\n return;\n }\n this.addConfigFromUrl(queryParams);\n }\n }\n\n private initWorkspaceForm(): void {\n this.configurationsFormGroup = this.formBuilder.group({\n configurations: this.formBuilder.array([])\n });\n\n this.patchForm();\n }\n\n private patchForm(): void {\n const control = <FormArray>this.configurationsFormGroup.controls['configurations'];\n this.configurations.forEach(workspace => {\n control.push(this.patchValues(workspace));\n });\n }\n\n private patchValues(workspace: WorkspaceConfiguration): FormGroup {\n return this.formBuilder.group({\n label: [workspace.label],\n config: [workspace.config],\n id: [workspace.id]\n });\n }\n\n /**\n * Subscribe to router events to clean query params when navigating away, but only when navigating between group or device context\n * Otherwise it breaks GC.\n */\n private subscribeToRouterEvents(): void {\n this.navigationSubscription = this.router.events.subscribe(event => {\n if (event instanceof NavigationStart) {\n const urlTree = this.router.parseUrl(event.url);\n if (\n urlTree.queryParams['config'] ||\n urlTree.queryParams['id'] ||\n urlTree.queryParams['label']\n ) {\n this.shouldCleanParams = true;\n } else {\n this.shouldCleanParams = false;\n }\n }\n });\n }\n\n private initializeContextSourceId(): void {\n const routeContext = this.contextRouteService.getContextData(this.activatedRoute);\n if (!routeContext) {\n this.workspaceConfigurationService.contextIdSignal.set(null);\n return;\n }\n const { context, contextData } = routeContext;\n if ([ViewContext.Device, ViewContext.Group].includes(context)) {\n this.workspaceConfigurationService.contextIdSignal.set(contextData?.id);\n }\n }\n}\n","@if (!silent) {\n <div\n class=\"dropdown\"\n #actionbar_dropdown=\"bs-dropdown\"\n [cdkTrapFocus]=\"actionbar_dropdown.isOpen\"\n dropdown\n [insideClick]=\"true\"\n >\n <button\n class=\"dropdown-toggle form-control l-h-tight d-flex a-i-center\"\n attr.aria-label=\"{{ currentConfiguration?.label }}\"\n tooltip=\"{{ 'Selected configuration' | translate }}\"\n placement=\"top\"\n container=\"body\"\n data-cy=\"current-configuration-dropdown-button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n dropdownToggle\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"list\"\n ></i>\n <div class=\"d-col text-left fit-w\">\n <span class=\"text-12\">\n {{ 'Configuration' | translate }}\n </span>\n <span class=\"text-10 text-muted text-truncate\">\n {{ currentConfiguration?.label }}\n </span>\n </div>\n <span class=\"caret m-r-16 m-l-4\"></span>\n </button>\n <div\n class=\"dropdown-menu dropdown-menu-wide dropdown-menu-action-bar\"\n *dropdownMenu\n >\n <div class=\"sticky-top separator-bottom p-t-8 p-b-8 p-l-16 p-r-16\">\n <p>\n <strong>{{ 'Data explorer configurations' | translate }}</strong>\n </p>\n <p>\n <small>{{ 'Easily switch and manage configurations.' | translate }}</small>\n </p>\n </div>\n <c8y-list-group class=\"no-border-last\">\n <form [formGroup]=\"configurationsFormGroup\">\n <div formArrayName=\"configurations\">\n @for (\n configuration of configurationsFormGroup.controls.configurations['controls'];\n track $index\n ) {\n <c8y-li\n class=\"p-0\"\n [dense]=\"true\"\n >\n <c8y-li-radio\n style=\"min-height: 48px\"\n [selected]=\"configuration.value.id === currentConfiguration.id\"\n (onSelect)=\"changeConfiguration($event, configuration.value)\"\n ></c8y-li-radio>\n\n <div class=\"d-flex a-i-center gap-8\">\n <div\n class=\"min-width-0\"\n [formGroupName]=\"$index\"\n >\n <label\n class=\"editable\"\n [ngClass]=\"{\n updated:\n configuration.controls.label.touched && configuration.controls.label.dirty\n }\"\n >\n <input\n class=\"form-control\"\n [style.width.ch]=\"configuration.value.label || 25\"\n [attr.aria-label]=\"'Configuration label' | translate\"\n placeholder=\"{{ 'Configuration 1' | translate }}\"\n type=\"text\"\n autocomplete=\"off\"\n required\n formControlName=\"label\"\n (blur)=\"updateConfigurationLabel(configuration.value)\"\n (keydown.enter)=\"\n $event.preventDefault(); updateConfigurationLabel(configuration.value)\n \"\n />\n </label>\n </div>\n\n <div class=\"flex-nogrow d-flex gap-8\">\n <button\n class=\"btn-dot btn m-0\"\n [attr.aria-label]=\"'Duplicate configuration' | translate\"\n tooltip=\"{{ 'Duplicate configuration' | translate }}\"\n placement=\"left\"\n (click)=\"addConfig(configuration.value.config)\"\n [delay]=\"500\"\n >\n <i c8yIcon=\"copy\"></i>\n </button>\n\n <button\n class=\"btn-dot btn btn-dot--danger\"\n [attr.aria-label]=\"'Remove configurations' | translate\"\n tooltip=\"{{\n (configuration.value.id === currentConfiguration.id\n ? activeConfigTooltip\n : removeConfigTooltip\n ) | translate\n }}\"\n placement=\"left\"\n [delay]=\"500\"\n (click)=\"$event.stopPropagation(); deleteConfiguration(configuration.value)\"\n [disabled]=\"configuration.value.id === currentConfiguration.id\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n </div>\n </c8y-li>\n }\n </div>\n </form>\n </c8y-list-group>\n <div class=\"sticky-bottom separator-top\">\n <div class=\"d-flex p-l-16 p-r-16 p-t-8 p-b-8\">\n <button\n class=\"btn btn-danger btn-sm flex-grow m-r-4\"\n (click)=\"clearAll()\"\n [disabled]=\"configurations.length < 2\"\n >\n <i [c8yIcon]=\"'delete'\"></i>\n {{ 'Delete all configurations' | translate }}\n </button>\n <button\n class=\"btn btn-default btn-sm flex-grow\"\n type=\"button\"\n (click)=\"addConfig()\"\n >\n <i [c8yIcon]=\"'add-circle-outline'\"></i>\n {{ 'Add configuration' | translate }}\n </button>\n </div>\n </div>\n </div>\n </div>\n}\n","import { Component } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { CoreModule, ModalLabels } from '@c8y/ngx-components';\nimport { ReportDashboardModule } from '@c8y/ngx-components/report-dashboard';\nimport { ContextDashboardService } from '@c8y/ngx-components/context-dashboard';\nimport { IManagedObject } from '@c8y/client';\n\n@Component({\n selector: 'c8y-create-new-report-modal',\n templateUrl: './create-new-report-modal.component.html',\n standalone: true,\n imports: [ReactiveFormsModule, CoreModule, ReportDashboardModule]\n})\nexport class CreateNewReportModalComponent {\n reportName = '';\n labels: ModalLabels = { cancel: gettext('Cancel'), ok: gettext('Send') };\n\n result = new Promise<IManagedObject>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n\n styling = {\n themeClass: 'dashboard-theme-light',\n headerClass: 'panel-title-regular'\n };\n\n readonly DEFAULT_DASHBOARD_ICON = 'th';\n readonly DEFAULT_DASHBOARD_PRIORITY = 5000;\n readonly DEFAULT_DASHBOARD_MARGIN = 12;\n private _resolve: (value: IManagedObject) => void;\n private _reject: (reason?: any) => void;\n constructor(private contextDashboardService: ContextDashboardService) {}\n\n async save() {\n const dashboard = {\n name: this.reportName,\n icon: this.DEFAULT_DASHBOARD_ICON,\n c8y_IsNavigatorNode: null,\n priority: this.DEFAULT_DASHBOARD_PRIORITY,\n description: '',\n widgetMargin: this.DEFAULT_DASHBOARD_MARGIN,\n classes: { [this.styling.headerClass]: true },\n widgetClasses: { [this.styling.headerClass]: true },\n translateDashboardTitle: true\n };\n try {\n const { name, icon, c8y_IsNavigatorNode, priority, description, translateDashboardTitle } =\n dashboard;\n const report = (\n await this.contextDashboardService.createReport({\n name,\n icon,\n c8y_IsNavigatorNode,\n priority,\n description,\n translateDashboardTitle\n } as Partial<IManagedObject>)\n ).data;\n\n await this.contextDashboardService.create(\n dashboard,\n undefined,\n `${this.contextDashboardService.REPORT_PARTIAL_NAME}${report.id}`\n );\n\n this._resolve(report);\n } catch (ex) {\n this._reject(ex);\n }\n }\n\n cancel() {\n this._reject();\n }\n}\n","<c8y-modal\n [title]=\"'Create new report with widget' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [disabled]=\"reportName === ''\"\n (onDismiss)=\"cancel()\"\n (onClose)=\"save()\"\n [labels]=\"labels\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"c8y-reports\"></span>\n </ng-container>\n\n <p class=\"text-center bg-component text-balance sticky-top p-l-24 p-r-24 p-t-8 p-b-8 separator-bottom\">\n {{' Create a new report with the Data points graph widget using the current configuration.' | translate}}\n </p>\n <div class=\"p-24 p-t-8\">\n <c8y-form-group>\n <label\n for=\"reportName\"\n translate\n >\n Report name\n </label>\n <input\n class=\"form-control\"\n id=\"reportName\"\n placeholder=\"{{ 'e.g. My data point Report' }}\"\n name=\"name\"\n type=\"text\"\n autocomplete=\"off\"\n required\n [(ngModel)]=\"reportName\"\n />\n <c8y-messages></c8y-messages>\n </c8y-form-group>\n </div>\n</c8y-modal>\n","import { Component, inject, Input } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { CoreModule, ModalLabels } from '@c8y/ngx-components';\nimport { Observable, merge, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { IIdentified, IManagedObject } from '@c8y/client';\nimport {\n ContextDashboardModule,\n ContextDashboardService,\n ContextDashboardType\n} from '@c8y/ngx-components/context-dashboard';\n\n@Component({\n selector: 'c8y-send-as-widget-to-dashboard-modal',\n templateUrl: './send-as-widget-to-dashboard-modal.component.html',\n standalone: true,\n imports: [ReactiveFormsModule, CoreModule, ContextDashboardModule]\n})\nexport class SendAsWidgetToDashboardModal {\n @Input() contextAsset: IIdentified;\n dashboards: IManagedObject[] = [];\n numberOfSelectedDashboards$: Observable<number>;\n form: ReturnType<typeof this.initForm>;\n result = new Promise<ReturnType<typeof this.initForm>['value']>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n labels: ModalLabels = { cancel: gettext('Cancel'), ok: gettext('Send') };\n private contextDashboardService = inject(ContextDashboardService);\n private _resolve: (value: ReturnType<typeof this.initForm>['value']) => void;\n private _reject: (reason?: any) => void;\n\n async ngOnInit() {\n const context = this.contextAsset.c8y_IsDevice\n ? ContextDashboardType.Device\n : ContextDashboardType.Group;\n\n this.dashboards = (\n await this.contextDashboardService.getContextDashboards(this.contextAsset as IManagedObject, [\n context\n ])\n ).data;\n\n if (this.dashboards.length > 0) {\n this.form = this.initForm();\n }\n this.numberOfSelectedDashboards$ = merge(this.form.valueChanges, of(this.form.value)).pipe(\n map(value => Object.values(value).filter(Boolean).length)\n );\n }\n\n save() {\n this._resolve(this.form.value);\n }\n\n cancel() {\n this._reject();\n }\n\n private initForm(): FormGroup<Record<string, FormControl<boolean>>> {\n const controls = this.dashboards.reduce(\n (acc, dashboard) => ({\n ...acc,\n [dashboard.id]: new FormControl(false)\n }),\n {}\n );\n return new FormGroup(controls);\n }\n}\n","<c8y-modal\n [title]=\"'Send as widget to dashboards' | translate\"\n [disabled]=\"!form || form.invalid\"\n [headerClasses]=\"'dialog-header'\"\n (onDismiss)=\"cancel()\"\n (onClose)=\"save()\"\n [labels]=\"labels\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"th\"></span>\n </ng-container>\n\n <c8y-list-group\n class=\"m-b-0 no-border-last\"\n *ngIf=\"form\"\n [formGroup]=\"form\"\n >\n <c8y-li>\n <p\n class=\"text-center text-medium\"\n *ngIf=\"numberOfSelectedDashboards$ | async as numberOfDashboards; else noSelectedDashboards\"\n translate\n [translateParams]=\"{ numberOfDashboards: numberOfDashboards }\"\n ngNonBindable\n >\n {{ numberOfDashboards }} dashboards selected for widget\n </p>\n <ng-template #noSelectedDashboards>\n <p\n class=\"text-center text-medium\"\n translate\n >\n Select one or more dashboards to send the Data points graph widget with the current\n configuration\n </p>\n </ng-template>\n </c8y-li>\n <c8y-li *ngFor=\"let dashboard of dashboards\">\n <c8y-li-checkbox\n [attr.data-cy]=\"'branding-apply-branding-to-app-checkbox-' + dashboard.id\"\n [formControlName]=\"dashboard.id\"\n ></c8y-li-checkbox>\n <c8y-li-icon class=\"p-l-0\">\n <i c8yIcon=\"{{ dashboard.icon }}\"></i>\n </c8y-li-icon>\n <div\n class=\"text-truncate\"\n title=\"{{ dashboard.name }}\"\n >\n {{ dashboard.name }}\n </div>\n </c8y-li>\n </c8y-list-group>\n\n <c8y-ui-empty-state\n [icon]=\"'th'\"\n [title]=\"'There are no dashboards defined.' | translate\"\n [subtitle]=\"'Add a dashboard first.' | translate\"\n *ngIf=\"dashboards.length === 0\"\n ></c8y-ui-empty-state>\n</c8y-modal>\n","import { Component, inject } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { CoreModule, ModalLabels } from '@c8y/ngx-components';\nimport { Observable, merge, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport {\n ReportDashboardModule,\n ReportDashboardService\n} from '@c8y/ngx-components/report-dashboard';\nimport { IManagedObject } from '@c8y/client';\n@Component({\n selector: 'c8y-send-as-widget-to-report-modal',\n templateUrl: './send-as-widget-to-report-modal.component.html',\n standalone: true,\n imports: [ReactiveFormsModule, CoreModule, ReportDashboardModule]\n})\nexport class SendAsWidgetToReportModal {\n reports: IManagedObject[] = [];\n numberOfSelectedReports$: Observable<number>;\n form: ReturnType<typeof this.initForm>;\n result = new Promise<ReturnType<typeof this.initForm>['value']>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n labels: ModalLabels = { cancel: gettext('Cancel'), ok: gettext('Send') };\n private reportDashboardService = inject(ReportDashboardService);\n private _resolve: (value: ReturnType<typeof this.initForm>['value']) => void;\n private _reject: (reason?: any) => void;\n async ngOnInit() {\n this.reports = (await this.reportDashboardService.listReports({ pageSize: 2000 })).data;\n if (this.reports.length > 0) {\n this.form = this.initForm();\n }\n this.numberOfSelectedReports$ = merge(this.form.valueChanges, of(this.form.value)).pipe(\n map(value => Object.values(value).filter(Boolean).length)\n );\n }\n save() {\n this._resolve(this.form.value);\n }\n cancel() {\n this._reject();\n }\n private initForm(): FormGroup<Record<string, FormControl<boolean>>> {\n const controls = this.reports.reduce(\n (acc, report) => ({\n ...acc,\n [report.id]: new FormControl(false)\n }),\n {}\n );\n return new FormGroup(controls);\n }\n}\n","<c8y-modal\n [title]=\"'Send as widget to reports' | translate\"\n [disabled]=\"!form || form.invalid\"\n [headerClasses]=\"'dialog-header'\"\n (onDismiss)=\"cancel()\"\n (onClose)=\"save()\"\n [labels]=\"labels\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"c8y-reports\"></span>\n </ng-container>\n\n <c8y-list-group\n class=\"m-b-0 no-border-last\"\n *ngIf=\"form\"\n [formGroup]=\"form\"\n >\n <c8y-li>\n <p\n class=\"text-center text-medium\"\n *ngIf=\"numberOfSelectedReports$ | async as numberOfReports; else noSelectedReports\"\n translate\n [translateParams]=\"{ numberOfReports: numberOfReports }\"\n ngNonBindable\n >\n {{ numberOfReports }} reports selected for widget\n </p>\n <ng-template #noSelectedReports>\n <p\n class=\"text-center text-medium\"\n translate\n >\n Select one or more reports to send the Data points graph widget with the current configuration\n </p>\n </ng-template>\n </c8y-li>\n <c8y-li *ngFor=\"let report of reports\">\n <c8y-li-checkbox\n [attr.data-cy]=\"'branding-apply-branding-to-app-checkbox-' + report.id\"\n [formControlName]=\"report.id\"\n ></c8y-li-checkbox>\n <c8y-li-icon class=\"p-l-0\">\n <i c8yIcon=\"{{ report.icon }}\"></i>\n </c8y-li-icon>\n <div class=\"text-truncate\" title=\"{{report.name}}\">{{ report.name }}</div>\n </c8y-li>\n </c8y-list-group>\n\n <c8y-ui-empty-state\n [icon]=\"'c8y-reports'\"\n [title]=\"'There are no reports defined.' | translate\"\n [subtitle]=\"'Add a report first.' | translate\"\n *ngIf=\"reports.length === 0\"\n ></c8y-ui-empty-state>\n</c8y-modal>\n","import { A11yModule } from '@angular/cdk/a11y';\nimport {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n inject,\n ViewChild\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { ActivatedRoute } from '@angular/router';\nimport { aggregationType, IIdentified, IManagedObject, MeasurementService } from '@c8y/client';\nimport {\n AlertService,\n CommonModule,\n ContextRouteService,\n CoreModule,\n DynamicComponentAlertAggregator,\n FormsModule,\n IconDirective,\n GainsightService,\n Permissions,\n ViewContext,\n WidgetTimeContextDateRangeService,\n DateAndTimeOptions,\n DatapointSyncService,\n ResizableGridComponent,\n Widget\n} from '@c8y/ngx-components';\nimport {\n AlarmEventSelectorModule,\n SelectedDatapoint\n} from '@c8y/ngx-components/alarm-event-selector';\nimport { ContextDashboardService } from '@c8y/ngx-components/context-dashboard';\nimport {\n DatapointAttributesFormConfig,\n DatapointSelectorModule,\n KPIDetails\n} from '@c8y/ngx-components/datapoint-selector';\nimport {\n DatapointsExportSelectorComponent,\n ExportConfig\n} from '@c8y/ngx-components/datapoints-export-selector';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n AlarmDetailsExtended,\n AlarmOrEventExtended,\n CHART_VIEW_CONTEXT,\n DatapointsGraphKPIDetails,\n DatapointsGraphWidgetConfig,\n EventDetailsExtended,\n PRODUCT_EXPERIENCE_DATA_EXPLORER_AND_GRAPH,\n TimeContextProps\n} from '@c8y/ngx-components/echart/models';\nimport {\n ChartAlarmsService,\n ChartEventsService,\n ChartHelpersService,\n ChartsComponent\n} from '@c8y/ngx-components/echart';\nimport { BsDropdownModule } from 'ngx-bootstrap/dropdown';\nimport { CollapseModule } from 'ngx-bootstrap/collapse';\nimport { TooltipModule } from 'ngx-bootstrap/tooltip';\nimport { PopoverModule } from 'ngx-bootstrap/popover';\nimport { Interval, INTERVALS } from '@c8y/ngx-components/interval-picker';\nimport { TimeContextComponent } from '@c8y/ngx-components/time-context';\nimport { BsModalService } from 'ngx-bootstrap/modal';\nimport { filter, fromEvent, map, Observable, startWith, take } from 'rxjs';\nimport { WorkspaceConfigComponent } from './configuration/workspace-configuration.component';\nimport { CreateNewReportModalComponent } from './create-new-report-modal/create-new-report-modal.component';\nimport { Settings } from './datapoint-explorer.model';\nimport { DataExplorerService } from './datapoint-explorer.service';\nimport { SendAsWidgetToDashboardModal } from './send-as-widget-to-dashboard-modal/send-as-widget-to-dashboard-modal.component';\nimport { SendAsWidgetToReportModal } from './send-as-widget-to-report-modal/send-as-widget-to-report-modal.component';\nimport { TimeInterval } from '@c8y/ngx-components/global-context';\n\n@Component({\n selector: 'c8y-datapoint-explorer',\n templateUrl: './datapoint-explorer.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [\n CoreModule,\n CommonModule,\n DatapointSelectorModule,\n AlarmEventSelectorModule,\n TooltipModule,\n PopoverModule,\n CollapseModule,\n TimeContextComponent,\n ChartsComponent,\n BsDropdownModule,\n FormsModule,\n A11yModule,\n DatapointsExportSelectorComponent,\n WorkspaceConfigComponent,\n IconDirective,\n ResizableGridComponent\n ],\n providers: [\n ChartEventsService,\n ChartAlarmsService,\n ChartHelpersService,\n WidgetTimeContextDateRangeService\n ]\n})\nexport class DatapointExplorerComponent {