@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
@@ -0,0 +1,306 @@
1
+ import {css, html, TemplateResult } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
+ import {WidgetSettings} from "../util/widget-settings";
4
+ import "../panels/attributes-panel";
5
+ import "../util/settings-panel";
6
+ import {i18next} from "@openremote/or-translate";
7
+ import {AttributeAction, AttributeActionEvent, AttributesSelectEvent} from "../panels/attributes-panel";
8
+ import {Asset, AssetDatapointIntervalQuery, AssetDatapointIntervalQueryFormula, Attribute, AttributeRef} from "@openremote/model";
9
+ import {ChartWidgetConfig} from "../widgets/chart-widget";
10
+ import {InputType, OrInputChangedEvent} from "@openremote/or-mwc-components/or-mwc-input";
11
+ import {TimePresetCallback} from "@openremote/or-chart";
12
+ import {when} from "lit/directives/when.js";
13
+
14
+ const styling = css`
15
+ .switch-container {
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: space-between;
19
+ }
20
+ `
21
+
22
+
23
+ @customElement("chart-settings")
24
+ export class ChartSettings extends WidgetSettings {
25
+
26
+ protected readonly widgetConfig!: ChartWidgetConfig
27
+
28
+ protected timePresetOptions: Map<string, TimePresetCallback> = new Map<string, TimePresetCallback>();
29
+ protected samplingOptions: Map<string, string> = new Map<string, string>();
30
+
31
+ public setTimePresetOptions(options: Map<string, TimePresetCallback>) {
32
+ this.timePresetOptions = options;
33
+ }
34
+
35
+ public setSamplingOptions(options: Map<string, string>) {
36
+ this.samplingOptions = options;
37
+ }
38
+
39
+ static get styles() {
40
+ return [...super.styles, styling];
41
+ }
42
+
43
+ protected render(): TemplateResult {
44
+ const attributeFilter: (attr: Attribute<any>) => boolean = (attr): boolean => {
45
+ return ["boolean", "positiveInteger", "positiveNumber", "number", "long", "integer", "bigInteger", "negativeInteger", "negativeNumber", "bigNumber", "integerByte", "direction"].includes(attr.type!)
46
+ };
47
+ const min = this.widgetConfig.chartOptions.options?.scales?.y?.min;
48
+ const max = this.widgetConfig.chartOptions.options?.scales?.y?.max;
49
+ const isMultiAxis = this.widgetConfig.rightAxisAttributes.length > 0;
50
+ const samplingValue = Array.from(this.samplingOptions.entries()).find((entry => entry[1] === this.widgetConfig.datapointQuery.type))![0]
51
+ const attributeLabelCallback = (asset: Asset, attribute: Attribute<any>, attributeLabel: string) => {
52
+ const isOnRightAxis = isMultiAxis && this.widgetConfig.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined;
53
+ return html`
54
+ <span>${asset.name}</span>
55
+ <span style="font-size:14px; color:grey;">${attributeLabel}</span>
56
+ ${when(isOnRightAxis, () => html`
57
+ <span style="position: absolute; right: 0; margin-bottom: 16px; font-size:14px; color:grey;"><or-translate value="right"></or-translate></span>
58
+ `)}
59
+ `
60
+ }
61
+ const attributeActionCallback = (attributeRef: AttributeRef): AttributeAction[] => {
62
+ return [{
63
+ icon: this.widgetConfig.rightAxisAttributes.includes(attributeRef) ? "arrow-right-bold" : "arrow-left-bold",
64
+ tooltip: i18next.t('dashboard.toggleAxis'),
65
+ disabled: false
66
+ }]
67
+ }
68
+ return html`
69
+ <div>
70
+ <!-- Attribute selection -->
71
+ <settings-panel displayName="attributes" expanded="${true}">
72
+ <attributes-panel .attributeRefs="${this.widgetConfig.attributeRefs}" multi="${true}" onlyDataAttrs="${true}" .attributeFilter="${attributeFilter}" style="padding-bottom: 12px;"
73
+ .attributeLabelCallback="${attributeLabelCallback}" .attributeActionCallback="${attributeActionCallback}"
74
+ @attribute-action="${(ev: AttributeActionEvent) => this.onAttributeAction(ev)}"
75
+ @attribute-select="${(ev: AttributesSelectEvent) => this.onAttributesSelect(ev)}"
76
+ ></attributes-panel>
77
+ </settings-panel>
78
+
79
+ <!-- Display options -->
80
+ <settings-panel displayName="display" expanded="${true}">
81
+ <div style="padding-bottom: 12px; display: flex; flex-direction: column; gap: 6px;">
82
+ <!-- Timeframe -->
83
+ <div>
84
+ <or-mwc-input .type="${InputType.SELECT}" label="${i18next.t('timeframeDefault')}" style="width: 100%;"
85
+ .options="${Array.from(this.timePresetOptions.keys())}" value="${this.widgetConfig.defaultTimePresetKey}"
86
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onTimePresetSelect(ev)}"
87
+ ></or-mwc-input>
88
+ </div>
89
+ <!-- Y Min/max options -->
90
+ <div>
91
+ <div class="switch-container">
92
+ <span><or-translate value="dashboard.allowTimerangeSelect"></or-translate></span>
93
+ <or-mwc-input .type="${InputType.SWITCH}" style="margin: 0 -10px;" .value="${!this.widgetConfig.showTimestampControls}"
94
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onTimestampControlsToggle(ev)}"
95
+ ></or-mwc-input>
96
+ </div>
97
+ <div class="switch-container">
98
+ <span><or-translate value="dashboard.showLegend"></or-translate></span>
99
+ <or-mwc-input .type="${InputType.SWITCH}" style="margin: 0 -10px;" .value="${this.widgetConfig.showLegend}"
100
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onShowLegendToggle(ev)}"
101
+ ></or-mwc-input>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </settings-panel>
106
+
107
+ <!-- Axis configuration -->
108
+ <settings-panel displayName="dashboard.axisConfig" expanded="${true}">
109
+ <div style="padding-bottom: 12px; display: flex; flex-direction: column; gap: 16px;">
110
+
111
+ <!-- Left axis configuration -->
112
+ <div>
113
+ ${when(isMultiAxis, () => html`
114
+ <div style="margin-bottom: 8px;">
115
+ <span><or-translate value="dashboard.leftAxis"></or-translate></span>
116
+ </div>
117
+ `)}
118
+ <div style="display: flex;">
119
+ ${max !== undefined ? html`
120
+ <or-mwc-input .type="${InputType.NUMBER}" label="${i18next.t('yAxis') + ' ' + i18next.t('max')}" .value="${max}" style="width: 100%;"
121
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueChange('left', 'max', ev)}"
122
+ ></or-mwc-input>
123
+ ` : html`
124
+ <or-mwc-input .type="${InputType.TEXT}" label="${i18next.t('yAxis') + ' ' + i18next.t('max')}" disabled="true" value="auto" style="width: 100%;"></or-mwc-input>
125
+ `}
126
+ <or-mwc-input .type="${InputType.SWITCH}" style="margin: 0 -10px 0 0;" .value="${max !== undefined}"
127
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueToggle('left', 'max', ev)}"
128
+ ></or-mwc-input>
129
+ </div>
130
+ <div style="display: flex; margin-top: 12px;">
131
+ ${min !== undefined ? html`
132
+ <or-mwc-input .type="${InputType.NUMBER}" label="${i18next.t('yAxis') + ' ' + i18next.t('min')}" .value="${min}" style="width: 100%;"
133
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueChange('left', 'min', ev)}"
134
+ ></or-mwc-input>
135
+ ` : html`
136
+ <or-mwc-input .type="${InputType.TEXT}" label="${i18next.t('yAxis') + ' ' + i18next.t('min')}" disabled="true" value="auto" style="width: 100%;"></or-mwc-input>
137
+ `}
138
+ <or-mwc-input .type="${InputType.SWITCH}" style="margin: 0 -10px 0 0;" .value="${min !== undefined}"
139
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueToggle('left', 'min', ev)}"
140
+ ></or-mwc-input>
141
+ </div>
142
+ </div>
143
+
144
+ <!-- Right axis configuration -->
145
+ ${when(isMultiAxis, () => {
146
+ const rightMin = this.widgetConfig.chartOptions.options?.scales?.y1?.min;
147
+ const rightMax = this.widgetConfig.chartOptions.options?.scales?.y1?.max;
148
+ return html`
149
+ <div>
150
+ <div style="margin-bottom: 8px;">
151
+ <span><or-translate value="dashboard.rightAxis"></or-translate></span>
152
+ </div>
153
+ <div style="display: flex;">
154
+ ${rightMax !== undefined ? html`
155
+ <or-mwc-input .type="${InputType.NUMBER}" label="${i18next.t('yAxis') + ' ' + i18next.t('max')}" .value="${rightMax}" style="width: 100%;"
156
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueChange('right', 'max', ev)}"
157
+ ></or-mwc-input>
158
+ ` : html`
159
+ <or-mwc-input .type="${InputType.TEXT}" label="${i18next.t('yAxis') + ' ' + i18next.t('max')}" disabled="true" value="auto"
160
+ style="width: 100%;"></or-mwc-input>
161
+ `}
162
+ <or-mwc-input .type="${InputType.SWITCH}" style="margin: 0 -10px 0 0;" .value="${rightMax !== undefined}"
163
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueToggle('right', 'max', ev)}"
164
+ ></or-mwc-input>
165
+ </div>
166
+ <div style="display: flex; margin-top: 12px;">
167
+ ${rightMin !== undefined ? html`
168
+ <or-mwc-input .type="${InputType.NUMBER}" label="${i18next.t('yAxis') + ' ' + i18next.t('min')}" .value="${rightMin}" style="width: 100%;"
169
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueChange('right', 'min', ev)}"
170
+ ></or-mwc-input>
171
+ ` : html`
172
+ <or-mwc-input .type="${InputType.TEXT}" label="${i18next.t('yAxis') + ' ' + i18next.t('min')}" disabled="true" value="auto"
173
+ style="width: 100%;"></or-mwc-input>
174
+ `}
175
+ <or-mwc-input .type="${InputType.SWITCH}" style="margin: 0 -10px 0 0;" .value="${rightMin !== undefined}"
176
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueToggle('right', 'min', ev)}"
177
+ ></or-mwc-input>
178
+ </div>
179
+ </div>
180
+ `
181
+ })}
182
+ </div>
183
+ </settings-panel>
184
+
185
+ <!-- Data sampling options -->
186
+ <settings-panel displayName="dataSampling" expanded="${true}">
187
+ <div style="padding-bottom: 12px; display: flex; flex-direction: column; gap: 12px;">
188
+ <div>
189
+ <or-mwc-input .type="${InputType.SELECT}" style="width: 100%" .options="${Array.from(this.samplingOptions.keys())}" .value="${samplingValue}"
190
+ label="${i18next.t('algorithm')}" @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onSamplingQueryChange(ev)}"
191
+ ></or-mwc-input>
192
+ </div>
193
+ <div>
194
+ ${this.getSamplingOptionsTemplate(this.widgetConfig.datapointQuery.type)}
195
+ </div>
196
+ </div>
197
+ </settings-panel>
198
+ </div>
199
+ `;
200
+ }
201
+
202
+ protected getSamplingOptionsTemplate(type: any): TemplateResult {
203
+ switch (type) {
204
+ case 'interval': {
205
+ const intervalQuery = this.widgetConfig.datapointQuery as AssetDatapointIntervalQuery;
206
+ const formulaOptions = [AssetDatapointIntervalQueryFormula.AVG, AssetDatapointIntervalQueryFormula.MIN, AssetDatapointIntervalQueryFormula.MAX];
207
+ return html`
208
+ <or-mwc-input .type="${InputType.SELECT}" style="width: 100%;" .options="${formulaOptions}"
209
+ .value="${intervalQuery.formula}" label="${i18next.t('algorithmMethod')}" @or-mwc-input-changed="${(event: OrInputChangedEvent) => {
210
+ intervalQuery.formula = event.detail.value;
211
+ this.notifyConfigUpdate();
212
+ }}"
213
+ ></or-mwc-input>
214
+ `;
215
+ }
216
+ default:
217
+ return html``;
218
+ }
219
+ }
220
+
221
+ // When a user clicks on ANY action in the attribute list, we want to switch between LEFT and RIGHT axis.
222
+ // Since that is the only action, there is no need to check the ev.action variable.
223
+ protected onAttributeAction(ev: AttributeActionEvent) {
224
+ if(this.widgetConfig.attributeRefs.indexOf(ev.detail.attributeRef) >= 0) {
225
+ if(this.widgetConfig.rightAxisAttributes.includes(ev.detail.attributeRef)) {
226
+ this.removeFromRightAxis(ev.detail.attributeRef);
227
+ } else {
228
+ this.addToRightAxis(ev.detail.attributeRef);
229
+ }
230
+ this.notifyConfigUpdate();
231
+ }
232
+ }
233
+
234
+ // When the list of attributeRefs is changed by the asset selector,
235
+ // we should remove the "right axis" references for the attributes that got removed.
236
+ // Also update the WidgetConfig attributeRefs field as usual
237
+ protected onAttributesSelect(ev: AttributesSelectEvent) {
238
+ const removedAttributeRefs = this.widgetConfig.attributeRefs.filter(ar => !ev.detail.attributeRefs.includes(ar));
239
+ removedAttributeRefs.forEach(raf => this.removeFromRightAxis(raf));
240
+ this.widgetConfig.attributeRefs = ev.detail.attributeRefs;
241
+ this.notifyConfigUpdate();
242
+ }
243
+
244
+ protected addToRightAxis(attributeRef: AttributeRef, notify = false) {
245
+ if(!this.widgetConfig.rightAxisAttributes.includes(attributeRef)) {
246
+ this.widgetConfig.rightAxisAttributes.push(attributeRef);
247
+ if(notify) {
248
+ this.notifyConfigUpdate();
249
+ }
250
+ }
251
+ }
252
+
253
+ protected removeFromRightAxis(attributeRef: AttributeRef, notify = false) {
254
+ if(this.widgetConfig.rightAxisAttributes.includes(attributeRef)) {
255
+ this.widgetConfig.rightAxisAttributes.splice(this.widgetConfig.rightAxisAttributes.indexOf(attributeRef), 1);
256
+ if(notify) {
257
+ this.notifyConfigUpdate();
258
+ }
259
+ }
260
+ }
261
+
262
+ protected onTimePresetSelect(ev: OrInputChangedEvent) {
263
+ this.widgetConfig.defaultTimePresetKey = ev.detail.value.toString();
264
+ this.notifyConfigUpdate();
265
+ }
266
+
267
+ protected onTimestampControlsToggle(ev: OrInputChangedEvent) {
268
+ this.widgetConfig.showTimestampControls = !ev.detail.value;
269
+ this.notifyConfigUpdate();
270
+ }
271
+
272
+ protected onShowLegendToggle(ev: OrInputChangedEvent) {
273
+ this.widgetConfig.showLegend = ev.detail.value;
274
+ this.notifyConfigUpdate();
275
+ }
276
+
277
+ protected setAxisMinMaxValue(axis: 'left' | 'right', type: 'min' | 'max', value?: number) {
278
+ if(axis === 'left') {
279
+ if(type === 'min') {
280
+ this.widgetConfig.chartOptions.options.scales.y.min = value;
281
+ } else {
282
+ this.widgetConfig.chartOptions.options.scales.y.max = value;
283
+ }
284
+ } else {
285
+ if(type === 'min') {
286
+ this.widgetConfig.chartOptions.options.scales.y1.min = value;
287
+ } else {
288
+ this.widgetConfig.chartOptions.options.scales.y1.max = value;
289
+ }
290
+ }
291
+ this.notifyConfigUpdate();
292
+ }
293
+
294
+ protected onMinMaxValueChange(axis: 'left' | 'right', type: 'min' | 'max', ev: OrInputChangedEvent) {
295
+ this.setAxisMinMaxValue(axis, type, ev.detail.value);
296
+ }
297
+
298
+ protected onMinMaxValueToggle(axis: 'left' | 'right', type: 'min' | 'max', ev: OrInputChangedEvent) {
299
+ this.setAxisMinMaxValue(axis, type, (ev.detail.value ? (type === 'min' ? 0 : 100) : undefined));
300
+ }
301
+
302
+ protected onSamplingQueryChange(ev: OrInputChangedEvent) {
303
+ this.widgetConfig.datapointQuery.type = this.samplingOptions.get(ev.detail.value)! as any;
304
+ this.notifyConfigUpdate();
305
+ }
306
+ }
@@ -0,0 +1,93 @@
1
+ import {html, TemplateResult} from "lit";
2
+ import {customElement} from "lit/decorators.js";
3
+ import {WidgetSettings} from "../util/widget-settings";
4
+ import {i18next} from "@openremote/or-translate";
5
+ import {GaugeWidgetConfig} from "../widgets/gauge-widget";
6
+ import {AttributesSelectEvent} from "../panels/attributes-panel";
7
+ import {Attribute} from "@openremote/model";
8
+ import { InputType, OrInputChangedEvent } from "@openremote/or-mwc-components/or-mwc-input";
9
+ import {ThresholdChangeEvent} from "../panels/thresholds-panel";
10
+ import "../panels/thresholds-panel";
11
+
12
+ @customElement("gauge-settings")
13
+ export class GaugeSettings extends WidgetSettings {
14
+
15
+ // Override of widgetConfig with extended type
16
+ protected widgetConfig!: GaugeWidgetConfig;
17
+
18
+ protected render(): TemplateResult {
19
+ const attributeFilter: (attr: Attribute<any>) => boolean = (attr): boolean => {
20
+ return ["positiveInteger", "positiveNumber", "number", "long", "integer", "bigInteger", "negativeInteger", "negativeNumber", "bigNumber", "integerByte", "direction"].includes(attr.type!)
21
+ };
22
+ return html`
23
+ <div>
24
+ <!-- Attribute selection -->
25
+ <settings-panel displayName="attributes" expanded="${true}">
26
+ <attributes-panel .attributeRefs="${this.widgetConfig.attributeRefs}" .attributeFilter="${attributeFilter}" style="padding-bottom: 12px;"
27
+ @attribute-select="${(ev: AttributesSelectEvent) => this.onAttributesSelect(ev)}"
28
+ ></attributes-panel>
29
+ </settings-panel>
30
+
31
+ <!-- Min/max and decimals options-->
32
+ <settings-panel displayName="values" expanded="${true}">
33
+ <div style="padding-bottom: 12px; display: flex; flex-direction: column; gap: 12px;">
34
+ <div style="display: flex; gap: 8px;">
35
+ <or-mwc-input .type="${InputType.NUMBER}" label="${i18next.t('min')}" .max="${this.widgetConfig.max}" .value="${this.widgetConfig.min}"
36
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueChange('min', ev)}"
37
+ ></or-mwc-input>
38
+ <or-mwc-input .type="${InputType.NUMBER}" label="${i18next.t('max')}" .min="${this.widgetConfig.min}" .value="${this.widgetConfig.max}"
39
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onMinMaxValueChange('max', ev)}"
40
+ ></or-mwc-input>
41
+ </div>
42
+ <div>
43
+ <or-mwc-input .type="${InputType.NUMBER}" style="width: 100%;" .value="${this.widgetConfig.decimals}" label="${i18next.t('decimals')}" .min="${0}"
44
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onDecimalsChange(ev)}"
45
+ ></or-mwc-input>
46
+ </div>
47
+ </div>
48
+ </settings-panel>
49
+
50
+ <!-- Thresholds panel -->
51
+ <settings-panel displayName="thresholds" expanded="${true}">
52
+ <thresholds-panel .thresholds="${this.widgetConfig.thresholds}" .valueType="${'number'}" style="padding-bottom: 12px;"
53
+ .min="${this.widgetConfig.min}" .max="${this.widgetConfig.max}"
54
+ @threshold-change="${(ev: ThresholdChangeEvent) => this.onThresholdChange(ev)}">
55
+ </thresholds-panel>
56
+ </settings-panel>
57
+ </div>
58
+ `;
59
+ }
60
+
61
+ // When new attributes get selected
62
+ // Update the displayName to the new asset and attribute name.
63
+ protected onAttributesSelect(ev: AttributesSelectEvent) {
64
+ this.widgetConfig.attributeRefs = ev.detail.attributeRefs;
65
+ if(ev.detail.attributeRefs.length === 1) {
66
+ const attributeRef = ev.detail.attributeRefs[0];
67
+ const asset = ev.detail.assets.find((asset) => asset.id === attributeRef.id);
68
+ this.setDisplayName!(asset ? `${asset.name} - ${attributeRef.name}` : `${attributeRef.name}`);
69
+ }
70
+ this.notifyConfigUpdate();
71
+ }
72
+
73
+ protected onMinMaxValueChange(type: 'min' | 'max', ev: OrInputChangedEvent) {
74
+ switch (type) {
75
+ case "max":
76
+ this.widgetConfig.max = ev.detail.value; break;
77
+ case "min":
78
+ this.widgetConfig.min = ev.detail.value; break;
79
+ }
80
+ this.notifyConfigUpdate();
81
+ }
82
+
83
+ protected onDecimalsChange(ev: OrInputChangedEvent) {
84
+ this.widgetConfig.decimals = ev.detail.value;
85
+ this.notifyConfigUpdate();
86
+ }
87
+
88
+ protected onThresholdChange(ev: ThresholdChangeEvent) {
89
+ this.widgetConfig.thresholds = ev.detail;
90
+ this.notifyConfigUpdate();
91
+ }
92
+
93
+ }
@@ -0,0 +1,175 @@
1
+ import {css, html, PropertyValues, TemplateResult } from "lit";
2
+ import { customElement, state } from "lit/decorators.js";
3
+ import {ImageWidgetConfig} from "../widgets/image-widget";
4
+ import {i18next} from "@openremote/or-translate";
5
+ import {AttributesSelectEvent} from "../panels/attributes-panel";
6
+ import {Asset, AssetModelUtil, AttributeRef} from "@openremote/model";
7
+ import { map } from "lit/directives/map.js";
8
+ import { InputType, OrInputChangedEvent } from "@openremote/or-mwc-components/or-mwc-input";
9
+ import {Util} from "@openremote/core";
10
+ import { when } from "lit/directives/when.js";
11
+ import {AssetWidgetSettings} from "../util/or-asset-widget";
12
+
13
+ const styling = css`
14
+ #marker-container {
15
+ display: flex;
16
+ justify-content: flex-end;
17
+ align-items: center;
18
+ }
19
+ `;
20
+
21
+ @customElement("image-settings")
22
+ export class ImageSettings extends AssetWidgetSettings {
23
+
24
+ // Override of widgetConfig with extended type
25
+ protected readonly widgetConfig!: ImageWidgetConfig;
26
+
27
+ static get styles() {
28
+ return [...super.styles, styling]
29
+ }
30
+
31
+ protected willUpdate(changedProps: PropertyValues) {
32
+ super.willUpdate(changedProps);
33
+ if(changedProps.has('widgetConfig') && this.widgetConfig) {
34
+ this.updateCoordinateMap(this.widgetConfig);
35
+ this.loadAssets();
36
+ }
37
+ }
38
+
39
+ protected loadAssets() {
40
+ const missingAssets = this.widgetConfig.attributeRefs.filter(ref => !this.isAttributeRefLoaded(ref));
41
+ if(missingAssets.length > 0) {
42
+ this.fetchAssets(this.widgetConfig.attributeRefs).then((assets) => {
43
+ if(assets === undefined) {
44
+ this.loadedAssets = [];
45
+ } else {
46
+ this.loadedAssets = assets;
47
+ }
48
+ });
49
+ }
50
+ }
51
+
52
+ protected render(): TemplateResult {
53
+ return html`
54
+ <div>
55
+ <!-- Attributes selector -->
56
+ <settings-panel displayName="'attributes" expanded="${true}">
57
+ <attributes-panel .attributeRefs="${this.widgetConfig.attributeRefs}" onlyDataAttrs="${false}" multi="${true}"
58
+ @attribute-select="${(ev: AttributesSelectEvent) => this.onAttributesSelect(ev)}"
59
+ ></attributes-panel>
60
+ </settings-panel>
61
+
62
+ <!-- Marker coordinates -->
63
+ <settings-panel displayName="dashboard.markerCoordinates" expanded="${true}">
64
+ <div style="display: flex; flex-direction: column; gap: 8px;">
65
+ ${map(this.draftCoordinateEntries(this.widgetConfig), template => template)}
66
+ </div>
67
+ </settings-panel>
68
+
69
+ <!-- Image settings -->
70
+ <settings-panel displayName="dashboard.imageSettings" expanded="${true}">
71
+ <div>
72
+ <or-mwc-input style="width: 100%;" type="${InputType.TEXT}" label="${i18next.t('dashboard.imageUrl')}" .value="${this.widgetConfig.imagePath}"
73
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onImageUrlUpdate(ev)}"
74
+ ></or-mwc-input>
75
+ </div>
76
+ </settings-panel>
77
+ </div>
78
+ `;
79
+ }
80
+
81
+ protected onAttributesSelect(ev: AttributesSelectEvent) {
82
+ this.widgetConfig.attributeRefs = ev.detail.attributeRefs;
83
+ this.notifyConfigUpdate();
84
+ }
85
+
86
+ protected onImageUrlUpdate(ev: OrInputChangedEvent) {
87
+ this.widgetConfig.imagePath = ev.detail.value;
88
+ this.notifyConfigUpdate();
89
+ }
90
+
91
+
92
+ /* -------------------------------------- */
93
+
94
+ // updates coordinate map according to the attributeRef entries per id
95
+ updateCoordinateMap(config: ImageWidgetConfig) {
96
+ for (let i = 0; i < config.attributeRefs.length; i++) {
97
+ const attributeRef = config.attributeRefs[i];
98
+ if (attributeRef === undefined) {
99
+ console.error('attributeRef is undefined');
100
+ return;
101
+ }
102
+ const index = config.markers.findIndex(m => m.attributeRef.id === attributeRef.id && m.attributeRef.name === attributeRef.name);
103
+ if (index === -1) {
104
+ config.markers.push({
105
+ attributeRef: attributeRef,
106
+ coordinates: [50, 50]
107
+ });
108
+ }
109
+ }
110
+ }
111
+
112
+ private draftCoordinateEntries(config: ImageWidgetConfig): TemplateResult[] {
113
+ const min = 0;
114
+ const max = 100;
115
+
116
+ if (config.markers.length > 0) {
117
+ return config.attributeRefs.map((attributeRef) => {
118
+ const marker = config.markers.find(m => m.attributeRef.id === attributeRef.id && m.attributeRef.name === attributeRef.name);
119
+ if(marker === undefined) {
120
+ console.error("A marker could not be found during drafting coordinate entries.");
121
+ return html``;
122
+ }
123
+ const index = config.markers.indexOf(marker);
124
+ const coordinates = marker.coordinates;
125
+ const asset = this.loadedAssets?.find(a => a.id === attributeRef.id);
126
+ let label: string | undefined;
127
+ if(asset) {
128
+ const attribute = asset.attributes![attributeRef.name!];
129
+ const descriptors = AssetModelUtil.getAttributeAndValueDescriptors(asset.type, attributeRef.name, attribute);
130
+ label = Util.getAttributeLabel(attribute, descriptors[0], asset.type, false);
131
+ }
132
+ return html`
133
+ <div id="marker-container">
134
+ <div style="flex: 1; display: flex; flex-direction: column;">
135
+ <span>${this.loadedAssets?.find(a => a.id === attributeRef.id)?.name}</span>
136
+ ${when(label, () => html`
137
+ <span style="color: gray;">${label}</span>
138
+ `)}
139
+ </div>
140
+ <div style="display: flex; gap: 8px;">
141
+ <or-mwc-input .disableSliderNumberInput="${true}" compact style="max-width: 64px;"
142
+ .type="${InputType.NUMBER}" .min="${min}" .max="${max}" .value="${coordinates[0]}"
143
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onCoordinateUpdate(index, 'x', ev.detail.value)}"
144
+ ></or-mwc-input>
145
+
146
+ <or-mwc-input .disableSliderNumberInput="${true}" compact style="max-width: 64px;"
147
+ .type="${InputType.NUMBER}" .min="${min}" .max="${max}" .value="${coordinates[1]}"
148
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onCoordinateUpdate(index, 'y', ev.detail.value)}"
149
+ ></or-mwc-input>
150
+ </div>
151
+ </div>
152
+ `;
153
+ });
154
+ } else {
155
+ return [
156
+ html`<span><or-translate value="noAttributeConnected"></or-translate></span>`
157
+ ];
158
+ }
159
+ }
160
+
161
+ protected onCoordinateUpdate(index: number, coordinate: 'x' | 'y', value: number) {
162
+ let coords = this.widgetConfig.markers[index].coordinates;
163
+ if(!coords) {
164
+ coords = [0, 0];
165
+ }
166
+ if(coordinate === 'x') {
167
+ coords[0] = value;
168
+ } else if(coordinate === 'y') {
169
+ coords[1] = value;
170
+ }
171
+ this.widgetConfig.markers[index].coordinates = coords;
172
+ this.notifyConfigUpdate();
173
+ }
174
+
175
+ }