@openremote/or-dashboard-builder 1.2.0-snapshot.20240512154942

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +156 -0
  2. package/build.gradle +15 -0
  3. package/lib/controls/dashboard-refresh-controls.d.ts +17 -0
  4. package/lib/controls/dashboard-refresh-controls.js +17 -0
  5. package/lib/controls/dashboard-refresh-controls.js.map +1 -0
  6. package/lib/index.d.ts +71 -0
  7. package/lib/index.js +285 -0
  8. package/lib/index.js.map +1 -0
  9. package/lib/or-dashboard-boardsettings.d.ts +19 -0
  10. package/lib/or-dashboard-boardsettings.js +121 -0
  11. package/lib/or-dashboard-boardsettings.js.map +1 -0
  12. package/lib/or-dashboard-browser.d.ts +9 -0
  13. package/lib/or-dashboard-browser.js +35 -0
  14. package/lib/or-dashboard-browser.js.map +1 -0
  15. package/lib/or-dashboard-engine.d.ts +4 -0
  16. package/lib/or-dashboard-engine.js +1 -0
  17. package/lib/or-dashboard-engine.js.map +1 -0
  18. package/lib/or-dashboard-keyhandler.d.ts +6 -0
  19. package/lib/or-dashboard-keyhandler.js +1 -0
  20. package/lib/or-dashboard-keyhandler.js.map +1 -0
  21. package/lib/or-dashboard-preview.d.ts +70 -0
  22. package/lib/or-dashboard-preview.js +155 -0
  23. package/lib/or-dashboard-preview.js.map +1 -0
  24. package/lib/or-dashboard-tree.d.ts +25 -0
  25. package/lib/or-dashboard-tree.js +62 -0
  26. package/lib/or-dashboard-tree.js.map +1 -0
  27. package/lib/or-dashboard-widgetcontainer.d.ts +23 -0
  28. package/lib/or-dashboard-widgetcontainer.js +20 -0
  29. package/lib/or-dashboard-widgetcontainer.js.map +1 -0
  30. package/lib/or-dashboard-widgetsettings.d.ts +15 -0
  31. package/lib/or-dashboard-widgetsettings.js +16 -0
  32. package/lib/or-dashboard-widgetsettings.js.map +1 -0
  33. package/lib/panels/assettypes-panel.d.ts +44 -0
  34. package/lib/panels/assettypes-panel.js +51 -0
  35. package/lib/panels/assettypes-panel.js.map +1 -0
  36. package/lib/panels/attributes-panel.d.ts +41 -0
  37. package/lib/panels/attributes-panel.js +126 -0
  38. package/lib/panels/attributes-panel.js.map +1 -0
  39. package/lib/panels/thresholds-panel.d.ts +39 -0
  40. package/lib/panels/thresholds-panel.js +129 -0
  41. package/lib/panels/thresholds-panel.js.map +1 -0
  42. package/lib/service/dashboard-service.d.ts +13 -0
  43. package/lib/service/dashboard-service.js +1 -0
  44. package/lib/service/dashboard-service.js.map +1 -0
  45. package/lib/service/widget-service.d.ts +8 -0
  46. package/lib/service/widget-service.js +1 -0
  47. package/lib/service/widget-service.js.map +1 -0
  48. package/lib/settings/attribute-input-settings.d.ts +13 -0
  49. package/lib/settings/attribute-input-settings.js +36 -0
  50. package/lib/settings/attribute-input-settings.js.map +1 -0
  51. package/lib/settings/chart-settings.d.ts +30 -0
  52. package/lib/settings/chart-settings.js +144 -0
  53. package/lib/settings/chart-settings.js.map +1 -0
  54. package/lib/settings/gauge-settings.d.ts +15 -0
  55. package/lib/settings/gauge-settings.js +37 -0
  56. package/lib/settings/gauge-settings.js.map +1 -0
  57. package/lib/settings/image-settings.d.ts +17 -0
  58. package/lib/settings/image-settings.js +52 -0
  59. package/lib/settings/image-settings.js.map +1 -0
  60. package/lib/settings/kpi-settings.d.ts +15 -0
  61. package/lib/settings/kpi-settings.js +47 -0
  62. package/lib/settings/kpi-settings.js.map +1 -0
  63. package/lib/settings/map-settings.d.ts +21 -0
  64. package/lib/settings/map-settings.js +72 -0
  65. package/lib/settings/map-settings.js.map +1 -0
  66. package/lib/settings/table-settings.d.ts +14 -0
  67. package/lib/settings/table-settings.js +33 -0
  68. package/lib/settings/table-settings.js.map +1 -0
  69. package/lib/style.d.ts +1 -0
  70. package/lib/style.js +99 -0
  71. package/lib/style.js.map +1 -0
  72. package/lib/util/or-asset-widget.d.ts +20 -0
  73. package/lib/util/or-asset-widget.js +1 -0
  74. package/lib/util/or-asset-widget.js.map +1 -0
  75. package/lib/util/or-widget.d.ts +31 -0
  76. package/lib/util/or-widget.js +1 -0
  77. package/lib/util/or-widget.js.map +1 -0
  78. package/lib/util/settings-panel.d.ts +9 -0
  79. package/lib/util/settings-panel.js +56 -0
  80. package/lib/util/settings-panel.js.map +1 -0
  81. package/lib/util/widget-config.d.ts +2 -0
  82. package/lib/util/widget-config.js +1 -0
  83. package/lib/util/widget-config.js.map +1 -0
  84. package/lib/util/widget-settings.d.ts +23 -0
  85. package/lib/util/widget-settings.js +1 -0
  86. package/lib/util/widget-settings.js.map +1 -0
  87. package/lib/widgets/attribute-input-widget.d.ts +24 -0
  88. package/lib/widgets/attribute-input-widget.js +31 -0
  89. package/lib/widgets/attribute-input-widget.js.map +1 -0
  90. package/lib/widgets/chart-widget.d.ts +25 -0
  91. package/lib/widgets/chart-widget.js +15 -0
  92. package/lib/widgets/chart-widget.js.map +1 -0
  93. package/lib/widgets/gauge-widget.d.ts +22 -0
  94. package/lib/widgets/gauge-widget.js +12 -0
  95. package/lib/widgets/gauge-widget.js.map +1 -0
  96. package/lib/widgets/image-widget.d.ts +25 -0
  97. package/lib/widgets/image-widget.js +54 -0
  98. package/lib/widgets/image-widget.js.map +1 -0
  99. package/lib/widgets/kpi-widget.d.ts +21 -0
  100. package/lib/widgets/kpi-widget.js +8 -0
  101. package/lib/widgets/kpi-widget.js.map +1 -0
  102. package/lib/widgets/map-widget.d.ts +39 -0
  103. package/lib/widgets/map-widget.js +9 -0
  104. package/lib/widgets/map-widget.js.map +1 -0
  105. package/lib/widgets/table-widget.d.ts +25 -0
  106. package/lib/widgets/table-widget.js +12 -0
  107. package/lib/widgets/table-widget.js.map +1 -0
  108. package/package.json +32 -0
  109. package/src/controls/dashboard-refresh-controls.ts +100 -0
  110. package/src/index.ts +731 -0
  111. package/src/or-dashboard-boardsettings.ts +249 -0
  112. package/src/or-dashboard-browser.ts +160 -0
  113. package/src/or-dashboard-engine.ts +17 -0
  114. package/src/or-dashboard-keyhandler.ts +25 -0
  115. package/src/or-dashboard-preview.ts +713 -0
  116. package/src/or-dashboard-tree.ts +304 -0
  117. package/src/or-dashboard-widgetcontainer.ts +155 -0
  118. package/src/or-dashboard-widgetsettings.ts +91 -0
  119. package/src/panels/assettypes-panel.ts +311 -0
  120. package/src/panels/attributes-panel.ts +304 -0
  121. package/src/panels/thresholds-panel.ts +285 -0
  122. package/src/service/dashboard-service.ts +89 -0
  123. package/src/service/widget-service.ts +48 -0
  124. package/src/settings/attribute-input-settings.ts +79 -0
  125. package/src/settings/chart-settings.ts +306 -0
  126. package/src/settings/gauge-settings.ts +93 -0
  127. package/src/settings/image-settings.ts +175 -0
  128. package/src/settings/kpi-settings.ts +106 -0
  129. package/src/settings/map-settings.ts +185 -0
  130. package/src/settings/table-settings.ts +92 -0
  131. package/src/style.ts +104 -0
  132. package/src/util/or-asset-widget.ts +110 -0
  133. package/src/util/or-widget.ts +60 -0
  134. package/src/util/settings-panel.ts +93 -0
  135. package/src/util/widget-config.ts +2 -0
  136. package/src/util/widget-settings.ts +58 -0
  137. package/src/widgets/attribute-input-widget.ts +143 -0
  138. package/src/widgets/chart-widget.ts +203 -0
  139. package/src/widgets/gauge-widget.ts +111 -0
  140. package/src/widgets/image-widget.ts +180 -0
  141. package/src/widgets/kpi-widget.ts +97 -0
  142. package/src/widgets/map-widget.ts +187 -0
  143. package/src/widgets/table-widget.ts +157 -0
  144. package/tsconfig.json +15 -0
  145. package/tsconfig.tsbuildinfo +1 -0
  146. package/webpack.config.js +10 -0
package/src/index.ts ADDED
@@ -0,0 +1,731 @@
1
+ import {css, html, LitElement, PropertyValues, unsafeCSS} from "lit";
2
+ import {customElement, property, query, state} from "lit/decorators.js";
3
+ import {when} from 'lit/directives/when.js';
4
+ import {styleMap} from 'lit/directives/style-map.js';
5
+ import "./or-dashboard-tree";
6
+ import "./or-dashboard-browser";
7
+ import "./or-dashboard-preview";
8
+ import "./or-dashboard-widgetsettings";
9
+ import "./or-dashboard-boardsettings";
10
+ import "./controls/dashboard-refresh-controls";
11
+ import {InputType, OrInputChangedEvent} from '@openremote/or-mwc-components/or-mwc-input';
12
+ import "@openremote/or-icon";
13
+ import {style} from "./style";
14
+ import {ClientRole, Dashboard, DashboardAccess, DashboardRefreshInterval, DashboardScalingPreset, DashboardScreenPreset, DashboardTemplate, DashboardWidget} from "@openremote/model";
15
+ import manager, {DefaultColor1, DefaultColor3, DefaultColor5, Util} from "@openremote/core";
16
+ import {ListItem} from "@openremote/or-mwc-components/or-mwc-list";
17
+ import {OrMwcTabItem} from "@openremote/or-mwc-components/or-mwc-tabs";
18
+ import "@openremote/or-mwc-components/or-mwc-tabs";
19
+ import {showSnackbar} from "@openremote/or-mwc-components/or-mwc-snackbar";
20
+ import {i18next} from "@openremote/or-translate";
21
+ import {showOkCancelDialog} from "@openremote/or-mwc-components/or-mwc-dialog";
22
+ import {DashboardKeyEmitter} from "./or-dashboard-keyhandler";
23
+ import {OrDashboardPreview} from "./or-dashboard-preview";
24
+ import {WidgetManifest} from "./util/or-widget";
25
+ import {ChartWidget} from "./widgets/chart-widget";
26
+ import {GaugeWidget} from "./widgets/gauge-widget";
27
+ import {IntervalSelectEvent, intervalToMillis} from "./controls/dashboard-refresh-controls";
28
+ import {ImageWidget} from "./widgets/image-widget";
29
+ import {KpiWidget} from "./widgets/kpi-widget";
30
+ import {MapWidget} from "./widgets/map-widget";
31
+ import {AttributeInputWidget} from "./widgets/attribute-input-widget";
32
+ import {TableWidget} from "./widgets/table-widget";
33
+
34
+ // language=CSS
35
+ const styling = css`
36
+
37
+ @media only screen and (min-width: 641px){
38
+ #tree {
39
+ min-width: 300px !important;
40
+ }
41
+ }
42
+ @media only screen and (max-width: 641px) {
43
+ #tree {
44
+ flex: 1 !important;
45
+ }
46
+ #builder {
47
+ max-height: inherit !important;
48
+ }
49
+ }
50
+
51
+ #tree {
52
+ flex: 0;
53
+ align-items: stretch;
54
+ z-index: 1;
55
+ box-shadow: rgb(0 0 0 / 21%) 0px 1px 3px 0px;
56
+ }
57
+
58
+ /* Header related styling */
59
+ #header {
60
+ background: var(--or-app-color1, ${unsafeCSS(DefaultColor1)});
61
+ }
62
+ #header-wrapper {
63
+ padding: 14px 30px;
64
+ display: flex;
65
+ flex-direction: row;
66
+ align-items: center;
67
+ border-bottom: 1px solid ${unsafeCSS(DefaultColor5)};
68
+ }
69
+ #header-title {
70
+ font-size: 18px;
71
+ }
72
+ #header-title > or-icon {
73
+ margin-right: 10px;
74
+ }
75
+ #header-actions {
76
+ flex: 1 1 auto;
77
+ text-align: right;
78
+ }
79
+ #header-actions-content {
80
+ display: flex;
81
+ flex-direction: row;
82
+ align-items: center;
83
+ float: right;
84
+ }
85
+
86
+ /* Header related styling */
87
+ @media screen and (max-width: 700px) {
88
+ #fullscreen-header-wrapper {
89
+ padding: 11px !important;
90
+ }
91
+ }
92
+ #fullscreen-header-wrapper {
93
+ min-height: 36px;
94
+ padding: 20px 30px 15px;
95
+ display: flex;
96
+ flex-direction: row;
97
+ align-items: center;
98
+ }
99
+ #fullscreen-header-title {
100
+ font-size: 18px;
101
+ font-weight: bold;
102
+ color: var(--or-app-color3, ${unsafeCSS(DefaultColor3)});
103
+ }
104
+ #fullscreen-header-title > or-mwc-input {
105
+ margin-right: 4px;
106
+ --or-icon-fill: ${unsafeCSS(DefaultColor3)};
107
+ }
108
+ #fullscreen-header-actions {
109
+ flex: 1 1 auto;
110
+ text-align: right;
111
+ }
112
+ #fullscreen-header-actions-content {
113
+ display: flex;
114
+ flex-direction: row;
115
+ align-items: center;
116
+ float: right;
117
+ }
118
+
119
+ /* ----------------------------- */
120
+ /* Editor/builder related styling */
121
+ #builder {
122
+ flex: 1 0 auto;
123
+ height: 100%;
124
+ }
125
+
126
+ /* ----------------------------- */
127
+ /* Sidebar related styling (drag and drop widgets / configuration) */
128
+ #sidebar {
129
+ vertical-align: top;
130
+ position: relative;
131
+ width: 300px;
132
+ background: white;
133
+ border-left: 1px solid ${unsafeCSS(DefaultColor5)};
134
+ }
135
+ #sidebar-widget-headeractions {
136
+ flex: 0;
137
+ display: flex;
138
+ flex-direction: row;
139
+ padding-right: 5px;
140
+ }
141
+ .settings-container {
142
+ display: flex;
143
+ flex-direction: column;
144
+ height: 100%;
145
+ }
146
+ #browser {
147
+ flex-grow: 1;
148
+ align-items: stretch;
149
+ z-index: 1;
150
+ max-width: 300px;
151
+ }
152
+
153
+ #save-btn { margin-left: 15px; }
154
+ #view-btn { margin-left: 18px; }
155
+
156
+ .small-btn {
157
+ height: 36px;
158
+ margin-top: -12px;
159
+ }
160
+
161
+ .hidescroll {
162
+ -ms-overflow-style: none; /* for Internet Explorer, Edge */
163
+ scrollbar-width: none; /* for Firefox */
164
+ }
165
+ .hidescroll::-webkit-scrollbar {
166
+ display: none; /* for Chrome, Safari, and Opera */
167
+ }
168
+ `;
169
+
170
+ export interface DashboardBuilderConfig {
171
+ // no configuration built yet
172
+ }
173
+ export const MAX_BREAKPOINT = 1000000;
174
+
175
+ // Enum to Menu String method
176
+ export function scalingPresetToString(scalingPreset: DashboardScalingPreset | undefined): string {
177
+ return (scalingPreset != null ? i18next.t("dashboard.presets." + scalingPreset.toLowerCase()) : "undefined");
178
+ }
179
+ export function dashboardAccessToString(access: DashboardAccess): string {
180
+ return i18next.t("dashboard.access." + access.toLowerCase());
181
+ }
182
+
183
+ export function sortScreenPresets(presets: DashboardScreenPreset[], largetosmall: boolean = false): DashboardScreenPreset[] {
184
+ return presets.sort((a, b) => {
185
+ if(a.breakpoint != null && b.breakpoint != null) {
186
+ if(a.breakpoint > b.breakpoint) {
187
+ return (largetosmall ? 1 : -1);
188
+ }
189
+ if(a.breakpoint < b.breakpoint) {
190
+ return (largetosmall ? 1 : -1);
191
+ }
192
+ }
193
+ return 0;
194
+ });
195
+ }
196
+
197
+ export function getActivePreset(gridWidth: number, presets: DashboardScreenPreset[]): DashboardScreenPreset | undefined {
198
+ let activePreset: DashboardScreenPreset | undefined;
199
+ sortScreenPresets(presets, true).forEach((preset) => {
200
+ if(preset.breakpoint != null && gridWidth <= preset.breakpoint) {
201
+ activePreset = preset;
202
+ }
203
+ });
204
+ return activePreset;
205
+ }
206
+
207
+ // A map containing a unique identifier as key, and a WidgetManifest (displayName, displayIcon and HTML tags) as value.
208
+ // We will use this for or-dashboard-browser and to initialise widgets, based on the html tags.
209
+ export const widgetTypes: Map<string, WidgetManifest> = new Map<string, WidgetManifest>();
210
+
211
+ export function registerWidgetTypes() {
212
+ widgetTypes.set("linechart", ChartWidget.getManifest());
213
+ widgetTypes.set("gauge", GaugeWidget.getManifest());
214
+ widgetTypes.set("image", ImageWidget.getManifest());
215
+ widgetTypes.set("kpi", KpiWidget.getManifest());
216
+ widgetTypes.set("map", MapWidget.getManifest());
217
+ widgetTypes.set("attributeinput", AttributeInputWidget.getManifest());
218
+ widgetTypes.set("table", TableWidget.getManifest());
219
+ }
220
+
221
+ @customElement("or-dashboard-builder")
222
+ export class OrDashboardBuilder extends LitElement {
223
+
224
+ // Importing Styles; the unsafe GridStack css, and all custom css
225
+ static get styles() {
226
+ return [styling, style]
227
+ }
228
+
229
+ @property()
230
+ protected readonly config: DashboardBuilderConfig | undefined;
231
+
232
+ @property() // (originally from URL)
233
+ protected readonly editMode: boolean = false;
234
+
235
+ @property()
236
+ protected readonly fullscreen: boolean = true;
237
+
238
+ @property() // ID of the selected dashboard (originally from URL)
239
+ protected readonly selectedId: string | undefined;
240
+
241
+ @property()
242
+ protected realm: string = manager.displayRealm;
243
+
244
+ @property() // REQUIRED userId
245
+ protected readonly userId!: string;
246
+
247
+ @property()
248
+ protected readonly readonly: boolean = true;
249
+
250
+
251
+ /* ------------------- */
252
+
253
+ @state()
254
+ protected dashboards: Dashboard[] | undefined;
255
+
256
+ @state() // Separate local template object
257
+ protected currentTemplate: DashboardTemplate | undefined;
258
+
259
+ @state()
260
+ protected selectedDashboard: Dashboard | undefined;
261
+
262
+ @state()
263
+ protected selectedWidgetId: string | undefined;
264
+
265
+ @state() // Used to toggle the SAVE button depending on whether changes have been made.
266
+ protected initialDashboardJSON: string | undefined;
267
+
268
+ @state() // Used to toggle the SAVE button depending on whether changes have been made.
269
+ protected initialTemplateJSON: string | undefined;
270
+
271
+ @state()
272
+ protected refreshInterval: DashboardRefreshInterval = DashboardRefreshInterval.OFF;
273
+
274
+ @state()
275
+ protected isInitializing: boolean;
276
+
277
+ @state()
278
+ protected isLoading: boolean;
279
+
280
+ @state() // Whether changes have been made
281
+ protected hasChanged: boolean;
282
+
283
+ @query('or-dashboard-preview')
284
+ protected dashboardPreview?: OrDashboardPreview;
285
+
286
+ protected refreshTimer?: ReturnType<typeof setInterval>;
287
+ private readonly keyEmitter: DashboardKeyEmitter = new DashboardKeyEmitter();
288
+
289
+
290
+ /* ------------- */
291
+
292
+ constructor() {
293
+ super();
294
+ this.isInitializing = true;
295
+ this.isLoading = true;
296
+ this.hasChanged = false;
297
+
298
+ registerWidgetTypes();
299
+
300
+ this.updateComplete.then(() => {
301
+ this.loadAllDashboards(this.realm);
302
+ });
303
+ }
304
+
305
+ connectedCallback() {
306
+ super.connectedCallback();
307
+ this.keyEmitter.addListener('delete', (_e: KeyboardEvent) => {
308
+ if(this.selectedWidgetId) {
309
+ const selectedWidget = this.selectedDashboard?.template?.widgets?.find(w => w.id == this.selectedWidgetId);
310
+ if(selectedWidget) { showOkCancelDialog(i18next.t('areYouSure'), i18next.t('dashboard.deleteWidgetWarning'), i18next.t('delete')).then((ok: boolean) => { if(ok) { this.deleteWidget(selectedWidget); }}); }
311
+ }
312
+ });
313
+ this.keyEmitter.addListener('deselect', (_e: KeyboardEvent) => { this.deselectWidget(); });
314
+ this.keyEmitter.addListener('save', (_e: KeyboardEvent) => { this.saveDashboard(); });
315
+ }
316
+
317
+ disconnectedCallback() {
318
+ super.disconnectedCallback();
319
+ this.keyEmitter.removeAllListeners();
320
+ }
321
+
322
+ willUpdate(changedProps: PropertyValues) {
323
+ super.willUpdate(changedProps);
324
+
325
+ this.isLoading = (this.dashboards == undefined);
326
+ this.isInitializing = (this.dashboards == undefined);
327
+
328
+ // On any update (except widget selection), check whether hasChanged should be updated.
329
+ if(!(changedProps.size === 1 && changedProps.has('selectedWidget'))) {
330
+ const dashboardEqual = Util.objectsEqual(this.selectedDashboard, this.initialDashboardJSON ? JSON.parse(this.initialDashboardJSON) : undefined);
331
+ const templateEqual = Util.objectsEqual(this.currentTemplate, this.initialTemplateJSON ? JSON.parse(this.initialTemplateJSON) : undefined);
332
+ this.hasChanged = (!dashboardEqual || !templateEqual);
333
+ }
334
+
335
+ // Support for realm switching
336
+ if(changedProps.has("realm") && changedProps.get("realm") !== undefined && this.realm) {
337
+ this.loadAllDashboards(this.realm);
338
+ }
339
+
340
+ // Any update on the dashboard
341
+ if(changedProps.has("selectedDashboard")) {
342
+ this.deselectWidget();
343
+ this.currentTemplate = this.selectedDashboard?.template;
344
+ if(!this.editMode) {
345
+ this.refreshInterval = this.currentTemplate?.refreshInterval || DashboardRefreshInterval.OFF;
346
+ changedProps.set('refreshInterval', this.refreshInterval);
347
+ }
348
+ this.dispatchEvent(new CustomEvent("selected", { detail: this.selectedDashboard }))
349
+ }
350
+
351
+ // When edit/view mode gets toggled
352
+ if(changedProps.has("editMode")) {
353
+ this.deselectWidget();
354
+ this.refreshInterval = DashboardRefreshInterval.OFF;
355
+ this.showDashboardTree = true;
356
+ if(this.editMode) {
357
+ this.refreshInterval = DashboardRefreshInterval.OFF;
358
+ } else {
359
+ this.refreshInterval = this.currentTemplate?.refreshInterval || DashboardRefreshInterval.OFF;
360
+ }
361
+ changedProps.set('refreshInterval', this.refreshInterval);
362
+ }
363
+
364
+ // When refresh interval has changed
365
+ if(changedProps.has("refreshInterval") && this.refreshInterval) {
366
+ this.setRefreshTimer(intervalToMillis(this.refreshInterval));
367
+ }
368
+ }
369
+
370
+ protected setRefreshTimer(millis: number | undefined) {
371
+ this.clearRefreshTimer();
372
+ if(millis !== undefined) {
373
+ this.refreshTimer = setInterval(() => {
374
+ this.deselectWidget();
375
+ this.dashboardPreview?.refreshWidgets();
376
+ }, millis)
377
+ }
378
+ }
379
+
380
+ protected clearRefreshTimer() {
381
+ if(this.refreshTimer) {
382
+ clearInterval(this.refreshTimer);
383
+ this.refreshTimer = undefined;
384
+ }
385
+ }
386
+
387
+ async loadAllDashboards(realm: string) {
388
+
389
+ // Getting dashboards
390
+ await manager.rest.api.DashboardResource.getAllRealmDashboards(realm).then((result) => {
391
+ this.dashboards = result.data;
392
+ }).catch((reason) => {
393
+ showSnackbar(undefined, "errorOccurred");
394
+ console.error(reason);
395
+ });
396
+
397
+ // Setting dashboard if selectedId is given by parent component
398
+ if(this.selectedId !== undefined) {
399
+ this.selectedDashboard = this.dashboards?.find(x => { return x.id == this.selectedId; });
400
+ }
401
+ }
402
+
403
+ /* ------------- */
404
+
405
+ // On every property update
406
+ updated(changedProperties: Map<string, any>) {
407
+ super.updated(changedProperties);
408
+
409
+ // Update on the Grid and its widget
410
+ if(changedProperties.has("currentTemplate")) {
411
+ if(this.selectedDashboard != null) {
412
+ this.selectedDashboard.template = this.currentTemplate;
413
+ }
414
+ }
415
+ }
416
+
417
+ /* ----------------- */
418
+
419
+ protected onWidgetCreation(widget: DashboardWidget): void {
420
+ const tempTemplate = JSON.parse(JSON.stringify(this.currentTemplate)) as DashboardTemplate;
421
+ if(!tempTemplate.widgets) {
422
+ tempTemplate.widgets = [];
423
+ }
424
+ tempTemplate.widgets.push(widget);
425
+ this.currentTemplate = tempTemplate;
426
+ }
427
+
428
+ deleteWidget(widget: DashboardWidget) {
429
+ if(this.currentTemplate != null && this.currentTemplate.widgets != null) {
430
+ const tempTemplate = this.currentTemplate;
431
+ tempTemplate.widgets = tempTemplate.widgets?.filter((x: DashboardWidget) => { return x.id != widget.id; });
432
+ this.currentTemplate = tempTemplate;
433
+ }
434
+ if(this.selectedWidgetId === widget.id) {
435
+ this.deselectWidget();
436
+ }
437
+ }
438
+
439
+ /* ------------------------------ */
440
+
441
+ selectWidget(widget: DashboardWidget): void {
442
+ const foundWidget = this.currentTemplate?.widgets?.find((x) => { return x.gridItem?.id == widget.gridItem?.id; });
443
+ if(foundWidget != null) {
444
+ this.selectedWidgetId = foundWidget.id;
445
+ } else {
446
+ console.error("The selected widget does not exist!");
447
+ }
448
+ }
449
+
450
+ deselectWidget() {
451
+ this.selectedWidgetId = undefined;
452
+ }
453
+
454
+ /* --------------------- */
455
+
456
+ selectDashboard(dashboard: Dashboard | undefined) {
457
+ if(this.dashboards != null) {
458
+ if(this.selectedDashboard && this.initialDashboardJSON) {
459
+ const indexOf = this.dashboards.indexOf(this.selectedDashboard);
460
+ if(indexOf) {
461
+ this.dashboards[indexOf] = JSON.parse(this.initialDashboardJSON) as Dashboard;
462
+ }
463
+ }
464
+ this.selectedDashboard = (dashboard ? this.dashboards.find((x) => { return x.id == dashboard.id; }) : undefined);
465
+ this.initialDashboardJSON = JSON.stringify(this.selectedDashboard);
466
+ this.initialTemplateJSON = JSON.stringify(this.selectedDashboard?.template);
467
+ }
468
+ }
469
+
470
+ changeDashboardName(value: string) {
471
+ if(this.selectedDashboard != null) {
472
+ const dashboard = this.selectedDashboard;
473
+ dashboard.displayName = value;
474
+ this.requestUpdate("selectedDashboard");
475
+ }
476
+ }
477
+
478
+ openDashboardInInsights() {
479
+ if(this.selectedDashboard != null) {
480
+ const insightsUrl: string = (window.location.origin + "/insights/?realm=" + manager.displayRealm + "#/view/" + this.selectedDashboard.id + "/true/"); // Just using relative URL to origin, as its enough for now.
481
+ window.open(insightsUrl)?.focus();
482
+ }
483
+ }
484
+
485
+ shareUrl(method: string) {
486
+ let url = window.location.href.replace("true", "false");
487
+ if(method == 'copy') {
488
+ navigator.clipboard.writeText(url);
489
+ } else if(method == 'tab') {
490
+ window.open(url, '_blank')?.focus()
491
+ }
492
+ }
493
+
494
+ /* ----------------------------------- */
495
+
496
+ saveDashboard() {
497
+ if(this.selectedDashboard != null && !this._isReadonly() && this._hasEditAccess()) {
498
+ this.isLoading = true;
499
+
500
+ // Saving object into the database
501
+ manager.rest.api.DashboardResource.update(this.selectedDashboard).then(() => {
502
+ if(this.dashboards != null && this.selectedDashboard != null) {
503
+ this.initialDashboardJSON = JSON.stringify(this.selectedDashboard);
504
+ this.initialTemplateJSON = JSON.stringify(this.selectedDashboard.template);
505
+ this.dashboards[this.dashboards?.indexOf(this.selectedDashboard)] = this.selectedDashboard;
506
+ this.currentTemplate = Object.assign({}, this.selectedDashboard.template);
507
+ showSnackbar(undefined, "dashboard.saveSuccessful");
508
+ }
509
+ }).catch((reason) => {
510
+ console.error(reason);
511
+ showSnackbar(undefined, "errorOccurred");
512
+ }).finally(() => {
513
+ this.isLoading = false;
514
+ })
515
+ } else {
516
+ console.error("The selected dashboard could not be found..");
517
+ showSnackbar(undefined, "errorOccurred");
518
+ }
519
+ }
520
+
521
+ protected _isReadonly(): boolean {
522
+ return this.readonly || !manager.hasRole(ClientRole.WRITE_INSIGHTS);
523
+ }
524
+ protected _hasEditAccess(): boolean {
525
+ return this.userId != null && (this.selectedDashboard?.editAccess == DashboardAccess.PRIVATE ? this.selectedDashboard?.ownerId == this.userId : true)
526
+ }
527
+ protected _hasViewAccess(): boolean {
528
+ return this.userId != null && (this.selectedDashboard?.viewAccess == DashboardAccess.PRIVATE ? this.selectedDashboard?.ownerId == this.userId : true)
529
+ }
530
+
531
+ /* ----------------- */
532
+
533
+ @state()
534
+ protected sidebarMenuIndex: number = 0;
535
+
536
+ @state()
537
+ protected showDashboardTree: boolean = true;
538
+
539
+ private readonly menuItems: ListItem[] = [
540
+ { icon: "content-copy", text: (i18next.t("copy") + " URL"), value: "copy" },
541
+ { icon: "open-in-new", text: i18next.t("dashboard.openInNewTab"), value: "tab" },
542
+ ];
543
+
544
+ private readonly tabItems: OrMwcTabItem[] = [
545
+ { name: i18next.t("dashboard.widgets") }, { name: i18next.t("settings") }
546
+ ];
547
+
548
+ // Rendering the page
549
+ render(): any {
550
+ if(window.matchMedia("(max-width: 600px)").matches && this.editMode) {
551
+ this.dispatchEvent(new CustomEvent('editToggle', { detail: false }));
552
+ this.showDashboardTree = true;
553
+ }
554
+ const builderStyles = {
555
+ display: (this.editMode && (this._isReadonly() || !this._hasEditAccess())) ? 'none' : undefined,
556
+ maxHeight: this.editMode ? "calc(100vh - 77px - 50px)" : "inherit"
557
+ };
558
+ return (!this.isInitializing || (this.dashboards != null && this.dashboards.length == 0)) ? html`
559
+ <div id="container">
560
+ ${(this.showDashboardTree) ? html`
561
+ <or-dashboard-tree id="tree" class="${this.selectedDashboard ? 'hideMobile' : undefined}"
562
+ .realm="${this.realm}" .hasChanged="${this.hasChanged}" .selected="${this.selectedDashboard}" .dashboards="${this.dashboards}" .showControls="${true}" .userId="${this.userId}" .readonly="${this._isReadonly()}"
563
+ @created="${(_event: CustomEvent) => { this.dispatchEvent(new CustomEvent('editToggle', { detail: true })); }}"
564
+ @updated="${(event: CustomEvent) => { this.dashboards = event.detail; this.selectedDashboard = undefined; }}"
565
+ @select="${(event: CustomEvent) => { this.selectDashboard(event.detail); }}"
566
+ ></or-dashboard-tree>
567
+ ` : undefined}
568
+ <div class="${this.selectedDashboard == null ? 'hideMobile' : undefined}" style="flex: 1; display: flex; flex-direction: column;">
569
+ ${this.editMode ? html`
570
+ <div id="header" class="hideMobile">
571
+ <div id="header-wrapper">
572
+ <div id="header-title">
573
+ <or-icon icon="view-dashboard"></or-icon>
574
+ ${this.selectedDashboard != null ? html`
575
+ <or-mwc-input .type="${InputType.TEXT}" min="1" max="1023" comfortable required outlined .label="${i18next.t('name') + '*\xa0'}"
576
+ ?readonly="${this._isReadonly()}" .value="${this.selectedDashboard.displayName}"
577
+ .disabled="${this.isLoading}" style="width: 300px;"
578
+ @or-mwc-input-changed="${(event: OrInputChangedEvent) => { this.changeDashboardName(event.detail.value); }}"
579
+ ></or-mwc-input>
580
+ ` : undefined}
581
+ </div>
582
+ <div id="header-actions">
583
+ <div id="header-actions-content">
584
+ ${when(this.selectedDashboard, () => html`
585
+ <or-mwc-input id="refresh-btn" class="small-btn" .disabled="${this.isLoading}" type="${InputType.BUTTON}" icon="refresh"
586
+ @or-mwc-input-changed="${() => { this.deselectWidget(); this.dashboardPreview?.refreshPreview(); }}">
587
+ </or-mwc-input>
588
+ <or-mwc-input id="responsive-btn" class="small-btn" .disabled="${this.isLoading}" type="${InputType.BUTTON}" icon="responsive"
589
+ @or-mwc-input-changed="${() => { this.dispatchEvent(new CustomEvent('fullscreenToggle', { detail: !this.fullscreen })); }}">
590
+ </or-mwc-input>
591
+ <or-mwc-input id="share-btn" class="small-btn" .disabled="${this.isLoading}" type="${InputType.BUTTON}" icon="open-in-new"
592
+ @or-mwc-input-changed="${() => { this.openDashboardInInsights(); }}">
593
+ </or-mwc-input>
594
+ <or-mwc-input id="save-btn" ?hidden="${this._isReadonly() || !this._hasEditAccess()}" .disabled="${this.isLoading || !this.hasChanged}" type="${InputType.BUTTON}" raised label="save"
595
+ @or-mwc-input-changed="${() => { this.saveDashboard(); }}">
596
+ </or-mwc-input>
597
+ <or-mwc-input id="view-btn" ?hidden="${this._isReadonly() || !this._hasViewAccess()}" type="${InputType.BUTTON}" outlined icon="eye" label="viewAsset"
598
+ @or-mwc-input-changed="${() => { this.dispatchEvent(new CustomEvent('editToggle', { detail: false })); }}">
599
+ </or-mwc-input>
600
+ `)}
601
+ </div>
602
+ </div>
603
+ </div>
604
+ </div>
605
+ ` : html`
606
+ <div id="fullscreen-header">
607
+ <div id="fullscreen-header-wrapper">
608
+ <div id="fullscreen-header-title" style="display: flex; align-items: center;">
609
+ <or-icon class="showMobile" style="margin-right: 10px;" icon="chevron-left" @click="${() => { this.selectedDashboard = undefined; }}"></or-icon>
610
+ <or-icon class="hideMobile" style="margin-right: 10px;" icon="menu" @click="${() => { this.showDashboardTree = !this.showDashboardTree; }}"></or-icon>
611
+ <span>${this.selectedDashboard?.displayName}</span>
612
+ </div>
613
+ <div id="fullscreen-header-actions">
614
+ <div id="fullscreen-header-actions-content">
615
+ ${when(this.selectedDashboard, () => html`
616
+ <or-mwc-input id="refresh-btn" class="small-btn" .disabled="${(this.selectedDashboard == null)}" type="${InputType.BUTTON}" icon="refresh"
617
+ @or-mwc-input-changed="${() => { this.deselectWidget(); this.dashboardPreview?.refreshPreview(); }}"
618
+ ></or-mwc-input>
619
+ <dashboard-refresh-controls .interval="${this.refreshInterval}" .readonly="${false}"
620
+ @interval-select="${(ev: IntervalSelectEvent) => this.onIntervalSelect(ev)}"
621
+ ></dashboard-refresh-controls>
622
+ <or-mwc-input id="share-btn" class="small-btn" .disabled="${(this.selectedDashboard == null)}" type="${InputType.BUTTON}" icon="open-in-new"
623
+ @or-mwc-input-changed="${() => { this.openDashboardInInsights(); }}"
624
+ ></or-mwc-input>
625
+ <or-mwc-input id="view-btn" class="hideMobile" ?hidden="${this.selectedDashboard == null || this._isReadonly() || !this._hasEditAccess()}" type="${InputType.BUTTON}" outlined icon="pencil" label="editAsset"
626
+ @or-mwc-input-changed="${() => { this.dispatchEvent(new CustomEvent('editToggle', { detail: true })); }}">
627
+ </or-mwc-input>
628
+ `)}
629
+ </div>
630
+ </div>
631
+ </div>
632
+ </div>
633
+ `}
634
+ <div id="content" style="flex: 1;">
635
+ <div id="container">
636
+ ${(this.editMode && (this._isReadonly() || !this._hasEditAccess())) ? html`
637
+ <div style="display: flex; justify-content: center; align-items: center; height: 100%;">
638
+ <span>${!this._hasEditAccess() ? i18next.t('noDashboardWriteAccess') : i18next.t('errorOccurred')}.</span>
639
+ </div>
640
+ ` : undefined}
641
+ <div id="builder" style="${styleMap(builderStyles)}">
642
+ ${(this.selectedDashboard != null) ? html`
643
+ <or-dashboard-preview class="editor" style="background: transparent;"
644
+ .realm="${this.realm}" .template="${this.currentTemplate}"
645
+ .selectedWidget="${this.selectedDashboard?.template?.widgets?.find(w => w.id == this.selectedWidgetId)}" .editMode="${this.editMode}"
646
+ .fullscreen="${this.fullscreen}" .readonly="${this._isReadonly()}"
647
+ @selected="${(event: CustomEvent) => { this.selectWidget(event.detail); }}"
648
+ @deselected="${() => { this.deselectWidget(); }}"
649
+ @created="${(event: CustomEvent) => { this.onWidgetCreation(event.detail); }}"
650
+ @changed="${(event: CustomEvent) => {
651
+ this.currentTemplate = event.detail.template;
652
+ }}"
653
+ ></or-dashboard-preview>
654
+ ` : html`
655
+ <div style="display: flex; justify-content: center; align-items: center; height: 100%;">
656
+ <span>${i18next.t('noDashboardSelected')}</span>
657
+ </div>
658
+ `}
659
+ </div>
660
+ ${when((this.selectedDashboard != null && this.editMode && !this._isReadonly() && this._hasEditAccess()), () => {
661
+ const selectedWidget = this.selectedDashboard?.template?.widgets?.find(w => w.id == this.selectedWidgetId);
662
+ return html`
663
+ <div id="sidebar" class="hideMobile">
664
+ ${this.selectedWidgetId != null ? html`
665
+ <div class="settings-container">
666
+ <div id="menu-header">
667
+ <div id="title-container">
668
+ <span id="title" title="${selectedWidget?.displayName}">${selectedWidget?.displayName}</span>
669
+ </div>
670
+ <div id="sidebar-widget-headeractions">
671
+ <or-mwc-input type="${InputType.BUTTON}" icon="delete" @or-mwc-input-changed="${() => {
672
+ showOkCancelDialog(i18next.t('areYouSure'), i18next.t('dashboard.deleteWidgetWarning'), i18next.t('delete')).then((ok: boolean) => {
673
+ if(ok) { this.deleteWidget(selectedWidget!); }
674
+ })
675
+ }}"></or-mwc-input>
676
+ <or-mwc-input type="${InputType.BUTTON}" icon="close" @or-mwc-input-changed="${() => { this.deselectWidget(); }}"></or-mwc-input>
677
+ </div>
678
+ </div>
679
+ <div id="content" class="hidescroll" style="flex: 1; overflow: hidden auto;">
680
+ <div style="position: relative;">
681
+ <or-dashboard-widgetsettings style="position: absolute;" .selectedWidget="${selectedWidget}" .realm="${this.realm}"
682
+ @delete="${(event: CustomEvent) => { this.deleteWidget(event.detail); }}"
683
+ @update="${(event: CustomEvent) => {
684
+ this.currentTemplate = Object.assign({}, this.selectedDashboard?.template);
685
+ if(event.detail.force) { this.deselectWidget(); this.dashboardPreview?.refreshPreview(); }}}"
686
+ ></or-dashboard-widgetsettings>
687
+ </div>
688
+ </div>
689
+ </div>
690
+ ` : undefined}
691
+ <div class="settings-container" style="${this.selectedWidgetId != null ? css`display: none` : null}">
692
+ <div style="border-bottom: 1px solid ${unsafeCSS(DefaultColor5)};">
693
+ <or-mwc-tabs .items="${this.tabItems}" noScroll @activated="${(event: CustomEvent) => { this.sidebarMenuIndex = event.detail.index; }}" style="pointer-events: ${this.selectedDashboard ? undefined : 'none'}"></or-mwc-tabs>
694
+ </div>
695
+ <div id="content" class="hidescroll" style="flex: 1; overflow: hidden auto;">
696
+ <div style="position: relative;">
697
+ <or-dashboard-browser id="browser" style="position: absolute; ${this.sidebarMenuIndex != 0 ? css`display: none` : null}"></or-dashboard-browser>
698
+ <or-dashboard-boardsettings style="position: absolute; ${this.sidebarMenuIndex != 1 ? css`display: none` : null}"
699
+ .dashboard="${this.selectedDashboard}" .showPerms="${this.selectedDashboard?.ownerId == this.userId}"
700
+ @update="${(event: CustomEvent) => {
701
+ this.currentTemplate = Object.assign({}, this.selectedDashboard?.template);
702
+ if(event.detail.force) { this.deselectWidget(); this.dashboardPreview?.refreshPreview(); }}}"
703
+ ></or-dashboard-boardsettings>
704
+ </div>
705
+ </div>
706
+ </div>
707
+ </div>
708
+ `
709
+ })}
710
+ </div>
711
+ </div>
712
+ </div>
713
+ </div>
714
+ ` : html`
715
+ <div id="container" style="justify-content: center; align-items: center;">
716
+ ${this.isInitializing ? html`
717
+ <span>${i18next.t("loading")}.</span>
718
+ ` : html`
719
+ <span>${i18next.t("errorOccurred")}.</span>
720
+ `}
721
+ </div>
722
+ `
723
+ }
724
+
725
+ protected onIntervalSelect(ev: IntervalSelectEvent) {
726
+ this.refreshInterval = ev.detail;
727
+ }
728
+
729
+ /* ======================== */
730
+
731
+ }