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

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 (167) 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-settingspanel.d.ts +44 -0
  25. package/lib/or-dashboard-settingspanel.js +216 -0
  26. package/lib/or-dashboard-settingspanel.js.map +1 -0
  27. package/lib/or-dashboard-tree.d.ts +25 -0
  28. package/lib/or-dashboard-tree.js +62 -0
  29. package/lib/or-dashboard-tree.js.map +1 -0
  30. package/lib/or-dashboard-widget.d.ts +18 -0
  31. package/lib/or-dashboard-widget.js +15 -0
  32. package/lib/or-dashboard-widget.js.map +1 -0
  33. package/lib/or-dashboard-widgetcontainer.d.ts +23 -0
  34. package/lib/or-dashboard-widgetcontainer.js +20 -0
  35. package/lib/or-dashboard-widgetcontainer.js.map +1 -0
  36. package/lib/or-dashboard-widgetsettings.d.ts +15 -0
  37. package/lib/or-dashboard-widgetsettings.js +16 -0
  38. package/lib/or-dashboard-widgetsettings.js.map +1 -0
  39. package/lib/panels/assettypes-panel.d.ts +44 -0
  40. package/lib/panels/assettypes-panel.js +51 -0
  41. package/lib/panels/assettypes-panel.js.map +1 -0
  42. package/lib/panels/attributes-panel.d.ts +41 -0
  43. package/lib/panels/attributes-panel.js +126 -0
  44. package/lib/panels/attributes-panel.js.map +1 -0
  45. package/lib/panels/thresholds-panel.d.ts +39 -0
  46. package/lib/panels/thresholds-panel.js +129 -0
  47. package/lib/panels/thresholds-panel.js.map +1 -0
  48. package/lib/service/dashboard-service.d.ts +13 -0
  49. package/lib/service/dashboard-service.js +1 -0
  50. package/lib/service/dashboard-service.js.map +1 -0
  51. package/lib/service/widget-service.d.ts +8 -0
  52. package/lib/service/widget-service.js +1 -0
  53. package/lib/service/widget-service.js.map +1 -0
  54. package/lib/settings/attribute-input-settings.d.ts +13 -0
  55. package/lib/settings/attribute-input-settings.js +36 -0
  56. package/lib/settings/attribute-input-settings.js.map +1 -0
  57. package/lib/settings/chart-settings.d.ts +30 -0
  58. package/lib/settings/chart-settings.js +144 -0
  59. package/lib/settings/chart-settings.js.map +1 -0
  60. package/lib/settings/gauge-settings.d.ts +15 -0
  61. package/lib/settings/gauge-settings.js +37 -0
  62. package/lib/settings/gauge-settings.js.map +1 -0
  63. package/lib/settings/image-settings.d.ts +17 -0
  64. package/lib/settings/image-settings.js +52 -0
  65. package/lib/settings/image-settings.js.map +1 -0
  66. package/lib/settings/kpi-settings.d.ts +15 -0
  67. package/lib/settings/kpi-settings.js +47 -0
  68. package/lib/settings/kpi-settings.js.map +1 -0
  69. package/lib/settings/map-settings.d.ts +21 -0
  70. package/lib/settings/map-settings.js +72 -0
  71. package/lib/settings/map-settings.js.map +1 -0
  72. package/lib/settings/table-settings.d.ts +14 -0
  73. package/lib/settings/table-settings.js +33 -0
  74. package/lib/settings/table-settings.js.map +1 -0
  75. package/lib/style.d.ts +1 -0
  76. package/lib/style.js +99 -0
  77. package/lib/style.js.map +1 -0
  78. package/lib/util/or-asset-widget.d.ts +20 -0
  79. package/lib/util/or-asset-widget.js +1 -0
  80. package/lib/util/or-asset-widget.js.map +1 -0
  81. package/lib/util/or-widget.d.ts +31 -0
  82. package/lib/util/or-widget.js +1 -0
  83. package/lib/util/or-widget.js.map +1 -0
  84. package/lib/util/settings-panel.d.ts +9 -0
  85. package/lib/util/settings-panel.js +56 -0
  86. package/lib/util/settings-panel.js.map +1 -0
  87. package/lib/util/widget-config.d.ts +2 -0
  88. package/lib/util/widget-config.js +1 -0
  89. package/lib/util/widget-config.js.map +1 -0
  90. package/lib/util/widget-settings.d.ts +23 -0
  91. package/lib/util/widget-settings.js +1 -0
  92. package/lib/util/widget-settings.js.map +1 -0
  93. package/lib/widgets/attribute-input-widget.d.ts +24 -0
  94. package/lib/widgets/attribute-input-widget.js +31 -0
  95. package/lib/widgets/attribute-input-widget.js.map +1 -0
  96. package/lib/widgets/chart-widget.d.ts +25 -0
  97. package/lib/widgets/chart-widget.js +15 -0
  98. package/lib/widgets/chart-widget.js.map +1 -0
  99. package/lib/widgets/gauge-widget.d.ts +22 -0
  100. package/lib/widgets/gauge-widget.js +12 -0
  101. package/lib/widgets/gauge-widget.js.map +1 -0
  102. package/lib/widgets/image-widget.d.ts +25 -0
  103. package/lib/widgets/image-widget.js +54 -0
  104. package/lib/widgets/image-widget.js.map +1 -0
  105. package/lib/widgets/kpi-widget.d.ts +21 -0
  106. package/lib/widgets/kpi-widget.js +8 -0
  107. package/lib/widgets/kpi-widget.js.map +1 -0
  108. package/lib/widgets/map-widget.d.ts +39 -0
  109. package/lib/widgets/map-widget.js +9 -0
  110. package/lib/widgets/map-widget.js.map +1 -0
  111. package/lib/widgets/or-base-widget.d.ts +15 -0
  112. package/lib/widgets/or-base-widget.js +1 -0
  113. package/lib/widgets/or-base-widget.js.map +1 -0
  114. package/lib/widgets/or-chart-widget.d.ts +36 -0
  115. package/lib/widgets/or-chart-widget.js +112 -0
  116. package/lib/widgets/or-chart-widget.js.map +1 -0
  117. package/lib/widgets/or-gauge-widget.d.ts +46 -0
  118. package/lib/widgets/or-gauge-widget.js +60 -0
  119. package/lib/widgets/or-gauge-widget.js.map +1 -0
  120. package/lib/widgets/or-kpi-widget.d.ts +44 -0
  121. package/lib/widgets/or-kpi-widget.js +63 -0
  122. package/lib/widgets/or-kpi-widget.js.map +1 -0
  123. package/lib/widgets/or-map-widget.d.ts +49 -0
  124. package/lib/widgets/or-map-widget.js +75 -0
  125. package/lib/widgets/or-map-widget.js.map +1 -0
  126. package/lib/widgets/table-widget.d.ts +25 -0
  127. package/lib/widgets/table-widget.js +12 -0
  128. package/lib/widgets/table-widget.js.map +1 -0
  129. package/package.json +32 -0
  130. package/src/controls/dashboard-refresh-controls.ts +100 -0
  131. package/src/index.ts +731 -0
  132. package/src/or-dashboard-boardsettings.ts +249 -0
  133. package/src/or-dashboard-browser.ts +160 -0
  134. package/src/or-dashboard-engine.ts +17 -0
  135. package/src/or-dashboard-keyhandler.ts +25 -0
  136. package/src/or-dashboard-preview.ts +713 -0
  137. package/src/or-dashboard-tree.ts +304 -0
  138. package/src/or-dashboard-widgetcontainer.ts +155 -0
  139. package/src/or-dashboard-widgetsettings.ts +91 -0
  140. package/src/panels/assettypes-panel.ts +311 -0
  141. package/src/panels/attributes-panel.ts +304 -0
  142. package/src/panels/thresholds-panel.ts +285 -0
  143. package/src/service/dashboard-service.ts +89 -0
  144. package/src/service/widget-service.ts +48 -0
  145. package/src/settings/attribute-input-settings.ts +79 -0
  146. package/src/settings/chart-settings.ts +306 -0
  147. package/src/settings/gauge-settings.ts +93 -0
  148. package/src/settings/image-settings.ts +175 -0
  149. package/src/settings/kpi-settings.ts +106 -0
  150. package/src/settings/map-settings.ts +185 -0
  151. package/src/settings/table-settings.ts +92 -0
  152. package/src/style.ts +104 -0
  153. package/src/util/or-asset-widget.ts +110 -0
  154. package/src/util/or-widget.ts +60 -0
  155. package/src/util/settings-panel.ts +93 -0
  156. package/src/util/widget-config.ts +2 -0
  157. package/src/util/widget-settings.ts +58 -0
  158. package/src/widgets/attribute-input-widget.ts +143 -0
  159. package/src/widgets/chart-widget.ts +203 -0
  160. package/src/widgets/gauge-widget.ts +111 -0
  161. package/src/widgets/image-widget.ts +180 -0
  162. package/src/widgets/kpi-widget.ts +97 -0
  163. package/src/widgets/map-widget.ts +187 -0
  164. package/src/widgets/table-widget.ts +157 -0
  165. package/tsconfig.json +15 -0
  166. package/tsconfig.tsbuildinfo +1 -0
  167. package/webpack.config.js +10 -0
@@ -0,0 +1,304 @@
1
+ import {css, html, LitElement, PropertyValues} from 'lit';
2
+ import {customElement, property} from 'lit/decorators.js';
3
+ import {InputType} from '@openremote/or-mwc-components/or-mwc-input';
4
+ import '@openremote/or-icon';
5
+ import {style} from './style';
6
+ import {Dashboard} from '@openremote/model';
7
+ import manager from '@openremote/core';
8
+ import {ListItem} from '@openremote/or-mwc-components/or-mwc-list';
9
+ import '@openremote/or-mwc-components/or-mwc-menu';
10
+ import {showOkCancelDialog} from '@openremote/or-mwc-components/or-mwc-dialog';
11
+ import {i18next} from '@openremote/or-translate';
12
+ import {showSnackbar} from '@openremote/or-mwc-components/or-mwc-snackbar';
13
+ import {style as OrAssetTreeStyle} from '@openremote/or-asset-tree';
14
+ import {DashboardService, DashboardSizeOption} from './service/dashboard-service';
15
+ import {isAxiosError} from '@openremote/rest';
16
+
17
+ // language=css
18
+ const treeStyling = css`
19
+ #header-btns {
20
+ display: flex;
21
+ flex-direction: row;
22
+ padding-right: 5px;
23
+ }
24
+
25
+ .node-container {
26
+ align-items: center;
27
+ padding-left: 10px;
28
+ }
29
+ `;
30
+
31
+ @customElement('or-dashboard-tree')
32
+ export class OrDashboardTree extends LitElement {
33
+
34
+ static get styles() {
35
+ return [style, treeStyling, OrAssetTreeStyle];
36
+ }
37
+
38
+ @property()
39
+ protected dashboards?: Dashboard[];
40
+
41
+ @property()
42
+ protected selected?: Dashboard;
43
+
44
+ @property()
45
+ protected readonly realm: string = manager.displayRealm;
46
+
47
+ @property() // REQUIRED
48
+ protected readonly userId!: string;
49
+
50
+ @property()
51
+ protected readonly readonly = true;
52
+
53
+ @property() // Whether the selected dashboard has been changed or not.
54
+ protected readonly hasChanged = false;
55
+
56
+ @property()
57
+ protected showControls = true;
58
+
59
+
60
+ /* --------------- */
61
+
62
+ shouldUpdate(changedProperties: PropertyValues) {
63
+ if (changedProperties.size === 1) {
64
+
65
+ // Prevent any update since it is not necessary in its current state.
66
+ // However, do update when dashboard is saved (aka when hasChanged is set back to false)
67
+ if (changedProperties.has('hasChanged') && this.hasChanged) {
68
+ return false;
69
+ }
70
+ }
71
+ return super.shouldUpdate(changedProperties);
72
+ }
73
+
74
+ updated(changedProperties: PropertyValues) {
75
+ if (!this.dashboards) {
76
+ this.getAllDashboards();
77
+ }
78
+ if (changedProperties.has('dashboards') && changedProperties.get('dashboards') != null) {
79
+ this.dispatchEvent(new CustomEvent('updated', {detail: this.dashboards}));
80
+ }
81
+ if (changedProperties.has('selected')) {
82
+ this.dispatchEvent(new CustomEvent('select', {detail: this.selected}));
83
+ }
84
+ }
85
+
86
+
87
+ /* ---------------------- */
88
+
89
+
90
+ // Gets ALL dashboards the user has access to.
91
+ // TODO: Optimize this by querying the database, or limit the JSON that is fetched.
92
+ private async getAllDashboards() {
93
+ return manager.rest.api.DashboardResource.getAllRealmDashboards(this.realm)
94
+ .then(result => {
95
+ this.dashboards = result.data;
96
+ }).catch(reason => {
97
+ console.error(reason);
98
+ showSnackbar(undefined, 'errorOccurred');
99
+ });
100
+ }
101
+
102
+ // Selects the dashboard (id).
103
+ // Will emit a "select" event during the lifecycle
104
+ private selectDashboard(id: string | Dashboard | undefined) {
105
+ if (typeof id === 'string') {
106
+ this.selected = this.dashboards?.find(dashboard => dashboard.id === id);
107
+ } else {
108
+ this.selected = id;
109
+ }
110
+ }
111
+
112
+ // Creates a dashboard, and adds it onto the list.
113
+ // Dispatch a "created" event, to let parent elements know a new dashboard has been created.
114
+ // It will automatically select the newly created dashboard.
115
+ private createDashboard(size: DashboardSizeOption) {
116
+ DashboardService.create(undefined, size, this.realm).then(d => {
117
+ if (!this.dashboards) {
118
+ this.dashboards = [d] as Dashboard[];
119
+ } else {
120
+ this.dashboards.push(d);
121
+ this.requestUpdate('dashboards');
122
+ }
123
+ this.dispatchEvent(new CustomEvent('created', {detail: {d}}));
124
+ this.selectDashboard(d);
125
+
126
+ }).catch(e => {
127
+ console.error(e);
128
+ if (isAxiosError(e) && e.response?.status === 404) {
129
+ showSnackbar(undefined, 'noDashboardFound');
130
+ } else {
131
+ showSnackbar(undefined, 'errorOccurred');
132
+ }
133
+ });
134
+ }
135
+
136
+ protected duplicateDashboard(dashboard: Dashboard) {
137
+ if (dashboard?.id !== null) {
138
+ if(this.hasChanged) {
139
+ this.showDiscardChangesModal().then(ok => {
140
+ if(ok) {
141
+ this._doDuplicateDashboard(dashboard);
142
+ }
143
+ })
144
+ } else {
145
+ this._doDuplicateDashboard(dashboard);
146
+ }
147
+ } else {
148
+ console.warn("Tried duplicating a NULL dashboard!");
149
+ }
150
+ }
151
+
152
+ protected _doDuplicateDashboard(dashboard: Dashboard) {
153
+ const newDashboard = JSON.parse(JSON.stringify(dashboard)) as Dashboard;
154
+ newDashboard.displayName = (newDashboard.displayName + ' copy');
155
+ DashboardService.create(newDashboard, undefined, this.realm).then(d => {
156
+ if (!this.dashboards) {
157
+ this.dashboards = [d] as Dashboard[];
158
+ } else {
159
+ this.dashboards.push(d);
160
+ this.requestUpdate('dashboards');
161
+ }
162
+ this.dispatchEvent(new CustomEvent('created', {detail: {d}}));
163
+ this.selectDashboard(d.id);
164
+ }).catch(e => {
165
+ console.error(e);
166
+ if (isAxiosError(e) && e.response?.status === 404) {
167
+ showSnackbar(undefined, 'noDashboardFound');
168
+ } else {
169
+ showSnackbar(undefined, 'errorOccurred');
170
+ }
171
+ });
172
+ }
173
+
174
+ private deleteDashboard(dashboard: Dashboard) {
175
+ if (dashboard.id != null) {
176
+ DashboardService.delete(dashboard.id, this.realm).then(() => {
177
+ this.getAllDashboards();
178
+ }).catch(reason => {
179
+ console.error(reason);
180
+ showSnackbar(undefined, 'errorOccurred');
181
+ });
182
+ }
183
+ }
184
+
185
+ /* ---------------------- */
186
+
187
+
188
+ // When a user clicks on a dashboard within the list...
189
+ protected onDashboardClick(dashboardId: string) {
190
+ if (dashboardId !== this.selected?.id) {
191
+ if (this.hasChanged) {
192
+ this.showDiscardChangesModal().then(ok => {
193
+ if(ok) this.selectDashboard(dashboardId);
194
+ })
195
+ } else {
196
+ this.selectDashboard(dashboardId);
197
+ }
198
+ }
199
+ }
200
+
201
+ protected showDiscardChangesModal(): Promise<boolean> {
202
+ return showOkCancelDialog(i18next.t('areYouSure'), i18next.t('confirmContinueDashboardModified'), i18next.t('discard'));
203
+ }
204
+
205
+ // Element render method
206
+ // TODO: Move dashboard filtering to separate method.
207
+ protected render() {
208
+ const dashboardItems: ListItem[][] = [];
209
+ if (this.dashboards && this.dashboards.length > 0) {
210
+ if (this.userId) {
211
+ const myDashboards: Dashboard[] = [];
212
+ const otherDashboards: Dashboard[] = [];
213
+ this.dashboards?.forEach(d => {
214
+ (d.ownerId === this.userId) ? myDashboards.push(d) : otherDashboards.push(d);
215
+ });
216
+ if (myDashboards.length > 0) {
217
+ const items: ListItem[] = [];
218
+ myDashboards.sort((a, b) => a.displayName ? a.displayName.localeCompare(b.displayName) : 0).forEach(d => {
219
+ items.push({icon: 'view-dashboard', text: d.displayName, value: d.id});
220
+ });
221
+ dashboardItems.push(items);
222
+ }
223
+ if (otherDashboards.length > 0) {
224
+ const items: ListItem[] = [];
225
+ otherDashboards.sort((a, b) => a.displayName ? a.displayName.localeCompare(b.displayName) : 0).forEach(d => {
226
+ items.push({icon: 'view-dashboard', text: d.displayName, value: d.id});
227
+ });
228
+ dashboardItems.push(items);
229
+ }
230
+ }
231
+ }
232
+ return html`
233
+ <div id="menu-header">
234
+ <div id="title-container">
235
+ <span id="title"><or-translate value="insights"></or-translate></span>
236
+ </div>
237
+
238
+ <!-- Controls header -->
239
+ ${this.showControls ? html`
240
+ <div id="header-btns">
241
+ ${this.selected != null ? html`
242
+ <or-mwc-input type="${InputType.BUTTON}" icon="close" @or-mwc-input-changed="${() => {
243
+ this.selectDashboard(undefined);
244
+ }}"></or-mwc-input>
245
+ ${!this.readonly ? html`
246
+ <or-mwc-input type="${InputType.BUTTON}" class="hideMobile" icon="content-copy"
247
+ @or-mwc-input-changed="${() => {
248
+ this.duplicateDashboard(this.selected!);
249
+ }}"
250
+ ></or-mwc-input>
251
+ <or-mwc-input type="${InputType.BUTTON}" icon="delete" @or-mwc-input-changed="${() => {
252
+ if (this.selected != null) {
253
+ showOkCancelDialog(i18next.t('areYouSure'), i18next.t('dashboard.deletePermanentWarning', {dashboard: this.selected.displayName}), i18next.t('delete')).then((ok: boolean) => {
254
+ if (ok) {
255
+ this.deleteDashboard(this.selected!);
256
+ }
257
+ });
258
+ }
259
+ }}"></or-mwc-input>
260
+ ` : undefined}
261
+ ` : undefined}
262
+ ${!this.readonly ? html`
263
+ <or-mwc-input type="${InputType.BUTTON}" class="hideMobile" icon="plus"
264
+ @or-mwc-input-changed="${() => {
265
+ this.createDashboard(DashboardSizeOption.DESKTOP);
266
+ }}"
267
+ ></or-mwc-input>
268
+ ` : undefined}
269
+ </div>
270
+ ` : undefined}
271
+ </div>
272
+
273
+ <!-- List of dashboards -->
274
+ <div id="content">
275
+ <div style="padding-top: 8px;">
276
+ ${dashboardItems.map((items, index) => {
277
+ return (items != null && items.length > 0) ? html`
278
+ <div style="padding: 8px 0;">
279
+ <span style="font-weight: 500; padding-left: 14px; color: #000000;">
280
+ <or-translate value="${(index === 0 ? 'dashboard.myDashboards' : 'dashboard.createdByOthers')}"></or-translate>
281
+ </span>
282
+ <div id="list-container" style="overflow: hidden;">
283
+ <ol id="list">
284
+ ${items.map((listItem: ListItem) => {
285
+ return html`
286
+ <li ?data-selected="${listItem.value === this.selected?.id}" @click="${(_evt: MouseEvent) => {
287
+ this.onDashboardClick(listItem.value);
288
+ }}">
289
+ <div class="node-container">
290
+ <span class="node-name">${listItem.text} </span>
291
+ </div>
292
+ </li>
293
+ `;
294
+ })}
295
+ </ol>
296
+ </div>
297
+ </div>
298
+ ` : undefined;
299
+ })}
300
+ </div>
301
+ </div>
302
+ `;
303
+ }
304
+ }
@@ -0,0 +1,155 @@
1
+ import {i18next} from "@openremote/or-translate";
2
+ import {html, LitElement, PropertyValues} from "lit";
3
+ import {customElement, property, query, state} from "lit/decorators.js";
4
+ import {when} from "lit/directives/when.js";
5
+ import {throttle} from "lodash";
6
+ import {style} from "./style";
7
+ import {DashboardWidget} from "@openremote/model";
8
+ import {OrWidget, WidgetManifest} from "./util/or-widget";
9
+ import {WidgetService} from "./service/widget-service";
10
+ import {WidgetConfig} from "./util/widget-config";
11
+
12
+ /* ------------------------------------ */
13
+
14
+ const elemTagName = "or-dashboard-widget-container"
15
+
16
+ @customElement(elemTagName)
17
+ export class OrDashboardWidgetContainer extends LitElement {
18
+
19
+ static tagName = elemTagName;
20
+
21
+ @property()
22
+ protected readonly widget!: DashboardWidget;
23
+
24
+ @property()
25
+ protected readonly editMode!: boolean;
26
+
27
+ @property()
28
+ protected loading: boolean = false;
29
+
30
+ @state()
31
+ protected orWidget?: OrWidget
32
+
33
+ @state()
34
+ protected error?: string; // untranslated error messages
35
+
36
+ @query("#widget-container")
37
+ protected containerElem?: Element;
38
+
39
+ protected resizeObserver?: ResizeObserver;
40
+ protected manifest?: WidgetManifest;
41
+
42
+
43
+ static get styles() {
44
+ return [style];
45
+ }
46
+
47
+ disconnectedCallback() {
48
+ super.disconnectedCallback()
49
+ this.resizeObserver?.disconnect();
50
+ }
51
+
52
+ shouldUpdate(changedProps: Map<PropertyKey, unknown>): boolean {
53
+ const changed = changedProps;
54
+
55
+ // Update config if some values in the spec are not set.
56
+ // Useful for when migrations have taken place.
57
+ if (this.widget) {
58
+ const manifest = WidgetService.getManifest(this.widget.widgetTypeId!);
59
+ if (manifest) {
60
+ this.widget.widgetConfig = WidgetService.correctToConfigSpec(manifest, this.widget.widgetConfig);
61
+ }
62
+ }
63
+
64
+ // Only update widget if certain properties of widget has changed.
65
+ // For example, when the 'gridItem' field changes, no update is needed since it doesn't apply here.
66
+ if (changedProps.has('widget') && this.widget) {
67
+ const oldVal = changedProps.get('widget') as DashboardWidget | undefined;
68
+ const idChanged = oldVal?.id !== this.widget?.id;
69
+ const nameChanged = oldVal?.displayName !== this.widget?.displayName;
70
+ const configChanged = JSON.stringify(oldVal?.widgetConfig) !== JSON.stringify(this.widget?.widgetConfig);
71
+ if (!(idChanged || nameChanged || configChanged)) {
72
+ changed.delete('widget');
73
+ }
74
+ }
75
+
76
+ return (changed.size === 0 ? false : super.shouldUpdate(changedProps));
77
+ }
78
+
79
+ willUpdate(changedProps: Map<string, any>) {
80
+ super.willUpdate(changedProps);
81
+
82
+ if (!this.manifest && this.widget) {
83
+ this.manifest = WidgetService.getManifest(this.widget.widgetTypeId!);
84
+ }
85
+
86
+ // Create widget
87
+ if (changedProps.has("widget") && this.widget) {
88
+ this.initializeWidgetElem(this.manifest!, this.widget.widgetConfig);
89
+ }
90
+ }
91
+
92
+ firstUpdated(changedProps: PropertyValues) {
93
+ super.firstUpdated(changedProps);
94
+
95
+ if (this.orWidget) {
96
+ const containerElem = this.containerElem;
97
+ if (containerElem) {
98
+ this.resizeObserver?.disconnect();
99
+ this.resizeObserver = new ResizeObserver(throttle(() => {
100
+ const minWidth = this.manifest!.minPixelWidth || 0;
101
+ const minHeight = this.manifest!.minPixelHeight || 0;
102
+ const isMinimumSize: boolean = (minWidth < containerElem.clientWidth) && (minHeight < containerElem.clientHeight);
103
+ this.error = (isMinimumSize ? undefined : "dashboard.widgetTooSmall");
104
+ }, 200));
105
+ this.resizeObserver.observe(containerElem);
106
+ } else {
107
+ console.error("gridItemElement could not be found!");
108
+ }
109
+ }
110
+ }
111
+
112
+ protected initializeWidgetElem(manifest: WidgetManifest, config: WidgetConfig) {
113
+ console.debug(`Initialising ${manifest.displayName} widget..`);
114
+ if (this.orWidget) {
115
+ this.orWidget.remove();
116
+ }
117
+ this.orWidget = manifest.getContentHtml(config);
118
+ this.orWidget.getDisplayName = () => this.widget.displayName;
119
+ this.orWidget.getEditMode = () => this.editMode
120
+ this.orWidget.getWidgetLocation = () => ({
121
+ x: this.widget.gridItem?.x,
122
+ y: this.widget.gridItem?.y,
123
+ w: this.widget.gridItem?.w,
124
+ h: this.widget.gridItem?.h
125
+ });
126
+ }
127
+
128
+ protected render() {
129
+ const showHeader = !!this.widget.displayName;
130
+ return html`
131
+ <div id="widget-container" style="height: calc(100% - 16px); padding: 8px 16px 8px 16px; display: flex; flex-direction: column;">
132
+
133
+ <!-- Container title -->
134
+ ${when(showHeader, () => html`
135
+ <div style="flex: 0 0 36px; display: flex; justify-content: space-between; align-items: center;">
136
+ <span class="panel-title" style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
137
+ ${this.widget.displayName?.toUpperCase()}
138
+ </span>
139
+ </div>
140
+ `)}
141
+
142
+ <!-- Content -->
143
+ <div style="flex: 1; max-height: ${showHeader ? 'calc(100% - 36px)' : '100%'};">
144
+ ${when((!this.error && !this.loading), () => html`
145
+ ${this.orWidget}
146
+ `, () => html`<or-translate value="${this.error ? this.error : "loading"}"></or-translate>`)}
147
+ </div>
148
+ </div>
149
+ `
150
+ }
151
+
152
+ public refreshContent(force: boolean) {
153
+ this.orWidget?.refreshContent(force);
154
+ }
155
+ }
@@ -0,0 +1,91 @@
1
+ import {Asset, DashboardWidget } from "@openremote/model";
2
+ import {html, LitElement, TemplateResult, unsafeCSS } from "lit";
3
+ import { customElement, property, state } from "lit/decorators.js";
4
+ import { InputType, OrInputChangedEvent } from "@openremote/or-mwc-components/or-mwc-input";
5
+ import {style} from './style';
6
+ import { i18next } from "@openremote/or-translate";
7
+ import { until } from "lit/directives/until.js";
8
+ import {WidgetService} from "./service/widget-service";
9
+ import {WidgetSettings, WidgetSettingsChangedEvent} from "./util/widget-settings";
10
+ import {WidgetManifest} from "./util/or-widget";
11
+ import { guard } from "lit/directives/guard.js";
12
+
13
+ const tableStyle = require("@material/data-table/dist/mdc.data-table.css");
14
+
15
+ /* ------------------------------------ */
16
+
17
+ @customElement("or-dashboard-widgetsettings")
18
+ export class OrDashboardWidgetsettings extends LitElement {
19
+
20
+ static get styles() {
21
+ return [unsafeCSS(tableStyle), style]
22
+ }
23
+
24
+ @property({type: Object})
25
+ protected selectedWidget!: DashboardWidget;
26
+
27
+ @state()
28
+ protected settingsElem?: WidgetSettings;
29
+
30
+ /* ------------------------------------ */
31
+
32
+
33
+ // Method to update the Grid. For example after changing a setting.
34
+ forceParentUpdate(changes: Map<string, any>, force: boolean = false) {
35
+ this.requestUpdate();
36
+ this.dispatchEvent(new CustomEvent('update', { detail: { changes: changes, force: force }}));
37
+ }
38
+
39
+ protected render() {
40
+ return html`
41
+ <div style="padding: 12px;">
42
+ <div>
43
+ <or-mwc-input .type="${InputType.TEXT}" style="width: 100%;" .value="${this.selectedWidget?.displayName}" label="${i18next.t('name')}"
44
+ @or-mwc-input-changed="${(event: OrInputChangedEvent) => this.setDisplayName(event.detail.value)}"
45
+ ></or-mwc-input>
46
+ </div>
47
+ </div>
48
+ <div>
49
+ ${guard([this.selectedWidget], () => html`
50
+ ${until(this.generateContent(this.selectedWidget!.widgetTypeId!), html`Loading...`)}
51
+ `)}
52
+ </div>
53
+ `;
54
+ }
55
+
56
+ protected setDisplayName(name?: string) {
57
+ this.selectedWidget!.displayName = name;
58
+ this.forceParentUpdate(new Map<string, any>([['widget', this.selectedWidget]]));
59
+ }
60
+
61
+ protected async generateContent(widgetTypeId: string): Promise<TemplateResult> {
62
+ if(!this.settingsElem || this.settingsElem.id !== this.selectedWidget.id) {
63
+ const manifest = WidgetService.getManifest(widgetTypeId);
64
+ this.settingsElem = this.initSettings(manifest);
65
+ }
66
+ return html`
67
+ ${this.settingsElem}
68
+ `;
69
+ }
70
+
71
+ protected initSettings(manifest: WidgetManifest): WidgetSettings {
72
+ const settingsElem = manifest.getSettingsHtml(this.selectedWidget!.widgetConfig);
73
+ settingsElem.id = this.selectedWidget.id!;
74
+ settingsElem.getDisplayName = () => this.selectedWidget.displayName;
75
+ settingsElem.setDisplayName = (name?: string) => this.setDisplayName(name);
76
+ settingsElem.getEditMode = () => true;
77
+ settingsElem.getWidgetLocation = () => ({
78
+ x: this.selectedWidget.gridItem?.x,
79
+ y: this.selectedWidget.gridItem?.y,
80
+ w: this.selectedWidget.gridItem?.w,
81
+ h: this.selectedWidget.gridItem?.h
82
+ });
83
+ settingsElem.addEventListener(WidgetSettingsChangedEvent.NAME, (ev: any) => this.onWidgetConfigChange(ev));
84
+ return settingsElem;
85
+ }
86
+
87
+ protected onWidgetConfigChange(ev: WidgetSettingsChangedEvent) {
88
+ this.selectedWidget!.widgetConfig = ev.detail;
89
+ this.forceParentUpdate(new Map<string, any>([['widget', this.selectedWidget]]));
90
+ }
91
+ }