Commit 65b7c139cf31cfc6dd78af08ca509924d00d5579
1 parent
8ee3f0bf
Widgets library import export support.
Showing
9 changed files
with
253 additions
and
78 deletions
@@ -55,7 +55,7 @@ | @@ -55,7 +55,7 @@ | ||
55 | ], | 55 | ], |
56 | "templateHtml": "<canvas id=\"pieChart\"></canvas>\n", | 56 | "templateHtml": "<canvas id=\"pieChart\"></canvas>\n", |
57 | "templateCss": "", | 57 | "templateCss": "", |
58 | - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = angular.isDefined(self.ctx.settings.borderWidth) ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", | 58 | + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", |
59 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}", | 59 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}", |
60 | "dataKeySettingsSchema": "{}\n", | 60 | "dataKeySettingsSchema": "{}\n", |
61 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" | 61 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" |
@@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http'; | @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http'; | ||
21 | import { PageLink } from '@shared/models/page/page-link'; | 21 | import { PageLink } from '@shared/models/page/page-link'; |
22 | import { PageData } from '@shared/models/page/page-data'; | 22 | import { PageData } from '@shared/models/page/page-data'; |
23 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | 23 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
24 | -import { WidgetType, widgetType, WidgetTypeData, widgetTypesData } from '@shared/models/widget.models'; | 24 | +import { Widget, WidgetType, widgetType, WidgetTypeData, widgetTypesData } from '@shared/models/widget.models'; |
25 | import { UtilsService } from '@core/services/utils.service'; | 25 | import { UtilsService } from '@core/services/utils.service'; |
26 | import { TranslateService } from '@ngx-translate/core'; | 26 | import { TranslateService } from '@ngx-translate/core'; |
27 | import { ResourcesService } from '../services/resources.service'; | 27 | import { ResourcesService } from '../services/resources.service'; |
@@ -119,6 +119,59 @@ export class WidgetService { | @@ -119,6 +119,59 @@ export class WidgetService { | ||
119 | defaultHttpOptions(ignoreLoading, ignoreErrors)); | 119 | defaultHttpOptions(ignoreLoading, ignoreErrors)); |
120 | } | 120 | } |
121 | 121 | ||
122 | + public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean, | ||
123 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Widget>> { | ||
124 | + return this.getBundleWidgetTypes(bundleAlias, isSystem, ignoreErrors, ignoreLoading).pipe( | ||
125 | + map((types) => { | ||
126 | + types = types.sort((a, b) => { | ||
127 | + let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); | ||
128 | + if (result === 0) { | ||
129 | + result = b.createdTime - a.createdTime; | ||
130 | + } | ||
131 | + return result; | ||
132 | + }); | ||
133 | + const widgetTypes = new Array<Widget>(); | ||
134 | + let top = 0; | ||
135 | + const lastTop = [0, 0, 0]; | ||
136 | + let col = 0; | ||
137 | + let column = 0; | ||
138 | + types.forEach((type) => { | ||
139 | + const widgetTypeInfo = toWidgetInfo(type); | ||
140 | + const sizeX = 8; | ||
141 | + const sizeY = Math.floor(widgetTypeInfo.sizeY); | ||
142 | + const widget: Widget = { | ||
143 | + typeId: type.id, | ||
144 | + isSystemType: isSystem, | ||
145 | + bundleAlias, | ||
146 | + typeAlias: widgetTypeInfo.alias, | ||
147 | + type: widgetTypeInfo.type, | ||
148 | + title: widgetTypeInfo.widgetName, | ||
149 | + sizeX, | ||
150 | + sizeY, | ||
151 | + row: top, | ||
152 | + col, | ||
153 | + config: JSON.parse(widgetTypeInfo.defaultConfig) | ||
154 | + }; | ||
155 | + | ||
156 | + widget.config.title = widgetTypeInfo.widgetName; | ||
157 | + | ||
158 | + widgetTypes.push(widget); | ||
159 | + top += sizeY; | ||
160 | + if (top > lastTop[column] + 10) { | ||
161 | + lastTop[column] = top; | ||
162 | + column++; | ||
163 | + if (column > 2) { | ||
164 | + column = 0; | ||
165 | + } | ||
166 | + top = lastTop[column]; | ||
167 | + col = column * 8; | ||
168 | + } | ||
169 | + }); | ||
170 | + return widgetTypes; | ||
171 | + }) | ||
172 | + ); | ||
173 | + } | ||
174 | + | ||
122 | public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, | 175 | public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, |
123 | ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { | 176 | ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { |
124 | return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, | 177 | return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, |
@@ -14,10 +14,16 @@ | @@ -14,10 +14,16 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { Widget } from '@app/shared/models/widget.models'; | 17 | +import { Widget, WidgetType } from '@app/shared/models/widget.models'; |
18 | import { DashboardLayoutId } from '@shared/models/dashboard.models'; | 18 | import { DashboardLayoutId } from '@shared/models/dashboard.models'; |
19 | +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | ||
19 | 20 | ||
20 | export interface ImportWidgetResult { | 21 | export interface ImportWidgetResult { |
21 | widget: Widget; | 22 | widget: Widget; |
22 | layoutId: DashboardLayoutId; | 23 | layoutId: DashboardLayoutId; |
23 | } | 24 | } |
25 | + | ||
26 | +export interface WidgetsBundleItem { | ||
27 | + widgetsBundle: WidgetsBundle; | ||
28 | + widgetTypes: WidgetType[]; | ||
29 | +} |
@@ -38,15 +38,18 @@ import { forkJoin, Observable, of } from 'rxjs'; | @@ -38,15 +38,18 @@ import { forkJoin, Observable, of } from 'rxjs'; | ||
38 | import { catchError, map, mergeMap } from 'rxjs/operators'; | 38 | import { catchError, map, mergeMap } from 'rxjs/operators'; |
39 | import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; | 39 | import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; |
40 | import { EntityService } from '@core/http/entity.service'; | 40 | import { EntityService } from '@core/http/entity.service'; |
41 | -import { Widget, WidgetSize } from '@shared/models/widget.models'; | 41 | +import { Widget, WidgetSize, WidgetType } from '@shared/models/widget.models'; |
42 | import { | 42 | import { |
43 | EntityAliasesDialogComponent, | 43 | EntityAliasesDialogComponent, |
44 | EntityAliasesDialogData | 44 | EntityAliasesDialogData |
45 | } from '@home/components/alias/entity-aliases-dialog.component'; | 45 | } from '@home/components/alias/entity-aliases-dialog.component'; |
46 | import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; | 46 | import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; |
47 | -import { ImportWidgetResult } from './import-export.models'; | 47 | +import { ImportWidgetResult, WidgetsBundleItem } from './import-export.models'; |
48 | import { EntityType } from '@shared/models/entity-type.models'; | 48 | import { EntityType } from '@shared/models/entity-type.models'; |
49 | import { UtilsService } from '@core/services/utils.service'; | 49 | import { UtilsService } from '@core/services/utils.service'; |
50 | +import { WidgetService } from '@core/http/widget.service'; | ||
51 | +import { NULL_UUID } from '@shared/models/id/has-uuid'; | ||
52 | +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | ||
50 | 53 | ||
51 | @Injectable() | 54 | @Injectable() |
52 | export class ImportExportService { | 55 | export class ImportExportService { |
@@ -57,6 +60,7 @@ export class ImportExportService { | @@ -57,6 +60,7 @@ export class ImportExportService { | ||
57 | private translate: TranslateService, | 60 | private translate: TranslateService, |
58 | private dashboardService: DashboardService, | 61 | private dashboardService: DashboardService, |
59 | private dashboardUtils: DashboardUtilsService, | 62 | private dashboardUtils: DashboardUtilsService, |
63 | + private widgetService: WidgetService, | ||
60 | private entityService: EntityService, | 64 | private entityService: EntityService, |
61 | private utils: UtilsService, | 65 | private utils: UtilsService, |
62 | private itembuffer: ItemBufferService, | 66 | private itembuffer: ItemBufferService, |
@@ -72,13 +76,7 @@ export class ImportExportService { | @@ -72,13 +76,7 @@ export class ImportExportService { | ||
72 | this.exportToPc(this.prepareDashboardExport(dashboard), name + '.json'); | 76 | this.exportToPc(this.prepareDashboardExport(dashboard), name + '.json'); |
73 | }, | 77 | }, |
74 | (e) => { | 78 | (e) => { |
75 | - let message = e; | ||
76 | - if (!message) { | ||
77 | - message = this.translate.instant('error.unknown-error'); | ||
78 | - } | ||
79 | - this.store.dispatch(new ActionNotificationShow( | ||
80 | - {message: this.translate.instant('dashboard.export-failed-error', {error: message}), | ||
81 | - type: 'error'})); | 79 | + this.handleExportError(e, 'dashboard.export-failed-error'); |
82 | } | 80 | } |
83 | ); | 81 | ); |
84 | } | 82 | } |
@@ -223,6 +221,119 @@ export class ImportExportService { | @@ -223,6 +221,119 @@ export class ImportExportService { | ||
223 | ); | 221 | ); |
224 | } | 222 | } |
225 | 223 | ||
224 | + public exportWidgetType(widgetTypeId: string) { | ||
225 | + this.widgetService.getWidgetTypeById(widgetTypeId).subscribe( | ||
226 | + (widgetType) => { | ||
227 | + if (isDefined(widgetType.bundleAlias)) { | ||
228 | + delete widgetType.bundleAlias; | ||
229 | + } | ||
230 | + let name = widgetType.name; | ||
231 | + name = name.toLowerCase().replace(/\W/g, '_'); | ||
232 | + this.exportToPc(this.prepareExport(widgetType), name + '.json'); | ||
233 | + }, | ||
234 | + (e) => { | ||
235 | + this.handleExportError(e, 'widget-type.export-failed-error'); | ||
236 | + } | ||
237 | + ); | ||
238 | + } | ||
239 | + | ||
240 | + public importWidgetType(bundleAlias: string): Observable<WidgetType> { | ||
241 | + return this.openImportDialog('widget-type.import', 'widget-type.widget-type-file').pipe( | ||
242 | + mergeMap((widgetType: WidgetType) => { | ||
243 | + if (!this.validateImportedWidgetType(widgetType)) { | ||
244 | + this.store.dispatch(new ActionNotificationShow( | ||
245 | + {message: this.translate.instant('widget-type.invalid-widget-type-file-error'), | ||
246 | + type: 'error'})); | ||
247 | + throw new Error('Invalid widget type file'); | ||
248 | + } else { | ||
249 | + widgetType.bundleAlias = bundleAlias; | ||
250 | + return this.widgetService.saveImportedWidgetType(widgetType); | ||
251 | + } | ||
252 | + }), | ||
253 | + catchError((err) => { | ||
254 | + return of(null); | ||
255 | + }) | ||
256 | + ); | ||
257 | + } | ||
258 | + | ||
259 | + public exportWidgetsBundle(widgetsBundleId: string) { | ||
260 | + this.widgetService.getWidgetsBundle(widgetsBundleId).subscribe( | ||
261 | + (widgetsBundle) => { | ||
262 | + const bundleAlias = widgetsBundle.alias; | ||
263 | + const isSystem = widgetsBundle.tenantId.id === NULL_UUID; | ||
264 | + this.widgetService.getBundleWidgetTypes(bundleAlias, isSystem).subscribe( | ||
265 | + (widgetTypes) => { | ||
266 | + const widgetsBundleItem: WidgetsBundleItem = { | ||
267 | + widgetsBundle: this.prepareExport(widgetsBundle), | ||
268 | + widgetTypes: [] | ||
269 | + }; | ||
270 | + for (const widgetType of widgetTypes) { | ||
271 | + if (isDefined(widgetType.bundleAlias)) { | ||
272 | + delete widgetType.bundleAlias; | ||
273 | + } | ||
274 | + widgetsBundleItem.widgetTypes.push(this.prepareExport(widgetType)); | ||
275 | + } | ||
276 | + let name = widgetsBundle.title; | ||
277 | + name = name.toLowerCase().replace(/\W/g, '_'); | ||
278 | + this.exportToPc(widgetsBundleItem, name + '.json'); | ||
279 | + }, | ||
280 | + (e) => { | ||
281 | + this.handleExportError(e, 'widgets-bundle.export-failed-error'); | ||
282 | + } | ||
283 | + ); | ||
284 | + }, | ||
285 | + (e) => { | ||
286 | + this.handleExportError(e, 'widgets-bundle.export-failed-error'); | ||
287 | + } | ||
288 | + ); | ||
289 | + } | ||
290 | + | ||
291 | + public importWidgetsBundle(): Observable<WidgetsBundle> { | ||
292 | + return this.openImportDialog('widgets-bundle.import', 'widgets-bundle.widgets-bundle-file').pipe( | ||
293 | + mergeMap((widgetsBundleItem: WidgetsBundleItem) => { | ||
294 | + if (!this.validateImportedWidgetsBundle(widgetsBundleItem)) { | ||
295 | + this.store.dispatch(new ActionNotificationShow( | ||
296 | + {message: this.translate.instant('widgets-bundle.invalid-widgets-bundle-file-error'), | ||
297 | + type: 'error'})); | ||
298 | + throw new Error('Invalid widgets bundle file'); | ||
299 | + } else { | ||
300 | + const widgetsBundle = widgetsBundleItem.widgetsBundle; | ||
301 | + return this.widgetService.saveWidgetsBundle(widgetsBundle).pipe( | ||
302 | + mergeMap((savedWidgetsBundle) => { | ||
303 | + const bundleAlias = savedWidgetsBundle.alias; | ||
304 | + const widgetTypes = widgetsBundleItem.widgetTypes; | ||
305 | + if (widgetTypes.length) { | ||
306 | + const saveWidgetTypesObservables: Array<Observable<WidgetType>> = []; | ||
307 | + for (const widgetType of widgetTypes) { | ||
308 | + widgetType.bundleAlias = bundleAlias; | ||
309 | + saveWidgetTypesObservables.push(this.widgetService.saveImportedWidgetType(widgetType)); | ||
310 | + } | ||
311 | + return forkJoin(saveWidgetTypesObservables).pipe( | ||
312 | + map(() => savedWidgetsBundle) | ||
313 | + ); | ||
314 | + } else { | ||
315 | + return of(savedWidgetsBundle); | ||
316 | + } | ||
317 | + } | ||
318 | + )); | ||
319 | + } | ||
320 | + }), | ||
321 | + catchError((err) => { | ||
322 | + return of(null); | ||
323 | + }) | ||
324 | + ); | ||
325 | + } | ||
326 | + | ||
327 | + private handleExportError(e: any, errorDetailsMessageId: string) { | ||
328 | + let message = e; | ||
329 | + if (!message) { | ||
330 | + message = this.translate.instant('error.unknown-error'); | ||
331 | + } | ||
332 | + this.store.dispatch(new ActionNotificationShow( | ||
333 | + {message: this.translate.instant(errorDetailsMessageId, {error: message}), | ||
334 | + type: 'error'})); | ||
335 | + } | ||
336 | + | ||
226 | private validateImportedDashboard(dashboard: Dashboard): boolean { | 337 | private validateImportedDashboard(dashboard: Dashboard): boolean { |
227 | if (isUndefined(dashboard.title) || isUndefined(dashboard.configuration)) { | 338 | if (isUndefined(dashboard.title) || isUndefined(dashboard.configuration)) { |
228 | return false; | 339 | return false; |
@@ -246,6 +357,34 @@ export class ImportExportService { | @@ -246,6 +357,34 @@ export class ImportExportService { | ||
246 | return true; | 357 | return true; |
247 | } | 358 | } |
248 | 359 | ||
360 | + private validateImportedWidgetType(widgetType: WidgetType): boolean { | ||
361 | + if (isUndefined(widgetType.name) | ||
362 | + || isUndefined(widgetType.descriptor)) { | ||
363 | + return false; | ||
364 | + } | ||
365 | + return true; | ||
366 | + } | ||
367 | + | ||
368 | + private validateImportedWidgetsBundle(widgetsBundleItem: WidgetsBundleItem): boolean { | ||
369 | + if (isUndefined(widgetsBundleItem.widgetsBundle)) { | ||
370 | + return false; | ||
371 | + } | ||
372 | + if (isUndefined(widgetsBundleItem.widgetTypes)) { | ||
373 | + return false; | ||
374 | + } | ||
375 | + const widgetsBundle = widgetsBundleItem.widgetsBundle; | ||
376 | + if (isUndefined(widgetsBundle.title)) { | ||
377 | + return false; | ||
378 | + } | ||
379 | + const widgetTypes = widgetsBundleItem.widgetTypes; | ||
380 | + for (const widgetType of widgetTypes) { | ||
381 | + if (!this.validateImportedWidgetType(widgetType)) { | ||
382 | + return false; | ||
383 | + } | ||
384 | + } | ||
385 | + return true; | ||
386 | + } | ||
387 | + | ||
249 | private saveImportedDashboard(dashboard: Dashboard): Observable<Dashboard> { | 388 | private saveImportedDashboard(dashboard: Dashboard): Observable<Dashboard> { |
250 | return this.dashboardService.saveDashboard(dashboard); | 389 | return this.dashboardService.saveDashboard(dashboard); |
251 | } | 390 | } |
@@ -114,7 +114,7 @@ export class DashboardWidgets implements Iterable<DashboardWidget> { | @@ -114,7 +114,7 @@ export class DashboardWidgets implements Iterable<DashboardWidget> { | ||
114 | updateRecords.push({ | 114 | updateRecords.push({ |
115 | widget: added.item, | 115 | widget: added.item, |
116 | widgetId: added.item.id, | 116 | widgetId: added.item.id, |
117 | - widgetLayout: this.widgetLayouts[added.item.id], | 117 | + widgetLayout: this.widgetLayouts ? this.widgetLayouts[added.item.id] : null, |
118 | operation: 'add' | 118 | operation: 'add' |
119 | }); | 119 | }); |
120 | }); | 120 | }); |
@@ -63,54 +63,10 @@ export class WidgetsTypesDataResolver implements Resolve<WidgetsData> { | @@ -63,54 +63,10 @@ export class WidgetsTypesDataResolver implements Resolve<WidgetsData> { | ||
63 | const widgetsBundle: WidgetsBundle = route.parent.data.widgetsBundle; | 63 | const widgetsBundle: WidgetsBundle = route.parent.data.widgetsBundle; |
64 | const bundleAlias = widgetsBundle.alias; | 64 | const bundleAlias = widgetsBundle.alias; |
65 | const isSystem = widgetsBundle.tenantId.id === NULL_UUID; | 65 | const isSystem = widgetsBundle.tenantId.id === NULL_UUID; |
66 | - return this.widgetsService.getBundleWidgetTypes(bundleAlias, | 66 | + return this.widgetsService.loadBundleLibraryWidgets(bundleAlias, |
67 | isSystem).pipe( | 67 | isSystem).pipe( |
68 | - map((types) => { | ||
69 | - types = types.sort((a, b) => { | ||
70 | - let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); | ||
71 | - if (result === 0) { | ||
72 | - result = b.createdTime - a.createdTime; | ||
73 | - } | ||
74 | - return result; | ||
75 | - }); | ||
76 | - const widgetTypes = new Array<Widget>(); | ||
77 | - let top = 0; | ||
78 | - const lastTop = [0, 0, 0]; | ||
79 | - let col = 0; | ||
80 | - let column = 0; | ||
81 | - types.forEach((type) => { | ||
82 | - const widgetTypeInfo = toWidgetInfo(type); | ||
83 | - const sizeX = 8; | ||
84 | - const sizeY = Math.floor(widgetTypeInfo.sizeY); | ||
85 | - const widget: Widget = { | ||
86 | - typeId: type.id, | ||
87 | - isSystemType: isSystem, | ||
88 | - bundleAlias, | ||
89 | - typeAlias: widgetTypeInfo.alias, | ||
90 | - type: widgetTypeInfo.type, | ||
91 | - title: widgetTypeInfo.widgetName, | ||
92 | - sizeX, | ||
93 | - sizeY, | ||
94 | - row: top, | ||
95 | - col, | ||
96 | - config: JSON.parse(widgetTypeInfo.defaultConfig) | ||
97 | - }; | ||
98 | - | ||
99 | - widget.config.title = widgetTypeInfo.widgetName; | ||
100 | - | ||
101 | - widgetTypes.push(widget); | ||
102 | - top += sizeY; | ||
103 | - if (top > lastTop[column] + 10) { | ||
104 | - lastTop[column] = top; | ||
105 | - column++; | ||
106 | - if (column > 2) { | ||
107 | - column = 0; | ||
108 | - } | ||
109 | - top = lastTop[column]; | ||
110 | - col = column * 8; | ||
111 | - } | ||
112 | - }); | ||
113 | - return { widgets: widgetTypes }; | 68 | + map((widgets) => { |
69 | + return { widgets }; | ||
114 | } | 70 | } |
115 | )); | 71 | )); |
116 | } | 72 | } |
@@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { Component, OnInit, ViewChild } from '@angular/core'; | 17 | +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; |
18 | import { Store } from '@ngrx/store'; | 18 | import { Store } from '@ngrx/store'; |
19 | import { AppState } from '@core/core.state'; | 19 | import { AppState } from '@core/core.state'; |
20 | import { PageComponent } from '@shared/components/page.component'; | 20 | import { PageComponent } from '@shared/components/page.component'; |
@@ -24,7 +24,7 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | @@ -24,7 +24,7 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | ||
24 | import { ActivatedRoute, Router } from '@angular/router'; | 24 | import { ActivatedRoute, Router } from '@angular/router'; |
25 | import { Authority } from '@shared/models/authority.enum'; | 25 | import { Authority } from '@shared/models/authority.enum'; |
26 | import { NULL_UUID } from '@shared/models/id/has-uuid'; | 26 | import { NULL_UUID } from '@shared/models/id/has-uuid'; |
27 | -import { Observable, of } from 'rxjs'; | 27 | +import { Observable, of, Subscription } from 'rxjs'; |
28 | import { Widget, widgetType } from '@app/shared/models/widget.models'; | 28 | import { Widget, widgetType } from '@app/shared/models/widget.models'; |
29 | import { WidgetService } from '@core/http/widget.service'; | 29 | import { WidgetService } from '@core/http/widget.service'; |
30 | import { map, mergeMap, share } from 'rxjs/operators'; | 30 | import { map, mergeMap, share } from 'rxjs/operators'; |
@@ -42,6 +42,7 @@ import { DeviceCredentials } from '@shared/models/device.models'; | @@ -42,6 +42,7 @@ import { DeviceCredentials } from '@shared/models/device.models'; | ||
42 | import { MatDialog } from '@angular/material/dialog'; | 42 | import { MatDialog } from '@angular/material/dialog'; |
43 | import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component'; | 43 | import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component'; |
44 | import { TranslateService } from '@ngx-translate/core'; | 44 | import { TranslateService } from '@ngx-translate/core'; |
45 | +import { ImportExportService } from '@home/components/import-export/import-export.service'; | ||
45 | 46 | ||
46 | @Component({ | 47 | @Component({ |
47 | selector: 'tb-widget-library', | 48 | selector: 'tb-widget-library', |
@@ -94,11 +95,12 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | @@ -94,11 +95,12 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | ||
94 | private router: Router, | 95 | private router: Router, |
95 | private widgetService: WidgetService, | 96 | private widgetService: WidgetService, |
96 | private dialogService: DialogService, | 97 | private dialogService: DialogService, |
98 | + private importExport: ImportExportService, | ||
97 | private dialog: MatDialog, | 99 | private dialog: MatDialog, |
98 | private translate: TranslateService) { | 100 | private translate: TranslateService) { |
99 | super(store); | 101 | super(store); |
100 | 102 | ||
101 | - this.authUser = getCurrentAuthUser(store); | 103 | + this.authUser = getCurrentAuthUser(this.store); |
102 | this.widgetsBundle = this.route.snapshot.data.widgetsBundle; | 104 | this.widgetsBundle = this.route.snapshot.data.widgetsBundle; |
103 | this.widgetsData = this.route.snapshot.data.widgetsData; | 105 | this.widgetsData = this.route.snapshot.data.widgetsData; |
104 | if (this.authUser.authority === Authority.TENANT_ADMIN) { | 106 | if (this.authUser.authority === Authority.TENANT_ADMIN) { |
@@ -119,7 +121,23 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | @@ -119,7 +121,23 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | ||
119 | if ($event) { | 121 | if ($event) { |
120 | $event.stopPropagation(); | 122 | $event.stopPropagation(); |
121 | } | 123 | } |
122 | - this.dialogService.todo(); | 124 | + this.importExport.importWidgetType(this.widgetsBundle.alias).subscribe( |
125 | + (widgetTypeInstance) => { | ||
126 | + if (widgetTypeInstance) { | ||
127 | + this.reload(); | ||
128 | + } | ||
129 | + } | ||
130 | + ); | ||
131 | + } | ||
132 | + | ||
133 | + private reload() { | ||
134 | + const bundleAlias = this.widgetsBundle.alias; | ||
135 | + const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; | ||
136 | + this.widgetService.loadBundleLibraryWidgets(bundleAlias, isSystem).subscribe( | ||
137 | + (widgets) => { | ||
138 | + this.widgetsData = {widgets}; | ||
139 | + } | ||
140 | + ); | ||
123 | } | 141 | } |
124 | 142 | ||
125 | openWidgetType($event: Event, widget?: Widget): void { | 143 | openWidgetType($event: Event, widget?: Widget): void { |
@@ -147,14 +165,14 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | @@ -147,14 +165,14 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | ||
147 | if ($event) { | 165 | if ($event) { |
148 | $event.stopPropagation(); | 166 | $event.stopPropagation(); |
149 | } | 167 | } |
150 | - this.dialogService.todo(); | 168 | + this.importExport.exportWidgetType(widget.typeId.id); |
151 | } | 169 | } |
152 | 170 | ||
153 | - removeWidgetType($event: Event, widget: Widget): Observable<boolean> { | 171 | + removeWidgetType($event: Event, widget: Widget): void { |
154 | if ($event) { | 172 | if ($event) { |
155 | $event.stopPropagation(); | 173 | $event.stopPropagation(); |
156 | } | 174 | } |
157 | - return this.dialogService.confirm( | 175 | + this.dialogService.confirm( |
158 | this.translate.instant('widget.remove-widget-type-title', {widgetName: widget.config.title}), | 176 | this.translate.instant('widget.remove-widget-type-title', {widgetName: widget.config.title}), |
159 | this.translate.instant('widget.remove-widget-type-text'), | 177 | this.translate.instant('widget.remove-widget-type-text'), |
160 | this.translate.instant('action.no'), | 178 | this.translate.instant('action.no'), |
@@ -169,13 +187,13 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | @@ -169,13 +187,13 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | ||
169 | }), | 187 | }), |
170 | map((result) => { | 188 | map((result) => { |
171 | if (result !== false) { | 189 | if (result !== false) { |
172 | - this.widgetsData.widgets.splice(this.widgetsData.widgets.indexOf(widget), 1); | 190 | + this.reload(); |
173 | return true; | 191 | return true; |
174 | } else { | 192 | } else { |
175 | return false; | 193 | return false; |
176 | } | 194 | } |
177 | } | 195 | } |
178 | - )); | 196 | + )).subscribe(); |
179 | } | 197 | } |
180 | 198 | ||
181 | } | 199 | } |
@@ -36,6 +36,7 @@ import {AppState} from '@core/core.state'; | @@ -36,6 +36,7 @@ import {AppState} from '@core/core.state'; | ||
36 | import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; | 36 | import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; |
37 | import {Authority} from '@shared/models/authority.enum'; | 37 | import {Authority} from '@shared/models/authority.enum'; |
38 | import {DialogService} from '@core/services/dialog.service'; | 38 | import {DialogService} from '@core/services/dialog.service'; |
39 | +import { ImportExportService } from '@home/components/import-export/import-export.service'; | ||
39 | 40 | ||
40 | @Injectable() | 41 | @Injectable() |
41 | export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableConfig<WidgetsBundle>> { | 42 | export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableConfig<WidgetsBundle>> { |
@@ -46,6 +47,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon | @@ -46,6 +47,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon | ||
46 | private dialogService: DialogService, | 47 | private dialogService: DialogService, |
47 | private widgetsService: WidgetService, | 48 | private widgetsService: WidgetService, |
48 | private translate: TranslateService, | 49 | private translate: TranslateService, |
50 | + private importExport: ImportExportService, | ||
49 | private datePipe: DatePipe, | 51 | private datePipe: DatePipe, |
50 | private router: Router) { | 52 | private router: Router) { |
51 | 53 | ||
@@ -124,11 +126,13 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon | @@ -124,11 +126,13 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon | ||
124 | } | 126 | } |
125 | 127 | ||
126 | importWidgetsBundle($event: Event) { | 128 | importWidgetsBundle($event: Event) { |
127 | - if ($event) { | ||
128 | - $event.stopPropagation(); | ||
129 | - } | ||
130 | - // TODO: | ||
131 | - this.dialogService.todo(); | 129 | + this.importExport.importWidgetsBundle().subscribe( |
130 | + (widgetsBundle) => { | ||
131 | + if (widgetsBundle) { | ||
132 | + this.config.table.updateData(); | ||
133 | + } | ||
134 | + } | ||
135 | + ); | ||
132 | } | 136 | } |
133 | 137 | ||
134 | openWidgetsBundle($event: Event, widgetsBundle: WidgetsBundle) { | 138 | openWidgetsBundle($event: Event, widgetsBundle: WidgetsBundle) { |
@@ -142,8 +146,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon | @@ -142,8 +146,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon | ||
142 | if ($event) { | 146 | if ($event) { |
143 | $event.stopPropagation(); | 147 | $event.stopPropagation(); |
144 | } | 148 | } |
145 | - // TODO: | ||
146 | - this.dialogService.todo(); | 149 | + this.importExport.exportWidgetsBundle(widgetsBundle.id.id); |
147 | } | 150 | } |
148 | 151 | ||
149 | onWidgetsBundleAction(action: EntityAction<WidgetsBundle>): boolean { | 152 | onWidgetsBundleAction(action: EntityAction<WidgetsBundle>): boolean { |
@@ -21,12 +21,12 @@ | @@ -21,12 +21,12 @@ | ||
21 | min-height: #{$size}px; | 21 | min-height: #{$size}px; |
22 | font-size: #{$size}px; | 22 | font-size: #{$size}px; |
23 | line-height: #{$size}px; | 23 | line-height: #{$size}px; |
24 | - svg { | 24 | +/* svg { |
25 | width: 24px; | 25 | width: 24px; |
26 | height: 24px; | 26 | height: 24px; |
27 | transform: scale($size/24); | 27 | transform: scale($size/24); |
28 | transform-origin: top; | 28 | transform-origin: top; |
29 | - } | 29 | + }*/ |
30 | } | 30 | } |
31 | 31 | ||
32 | @mixin tb-mat-icon-button-size($size) { | 32 | @mixin tb-mat-icon-button-size($size) { |