@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,311 @@
1
+ import {css, html, LitElement, PropertyValues, TemplateResult} from "lit";
2
+ import {customElement, property, state} from "lit/decorators.js";
3
+ import {Asset, AssetDescriptor, AssetModelUtil, AssetQuery, AssetTypeInfo} from "@openremote/model";
4
+ import {getContentWithMenuTemplate} from "@openremote/or-mwc-components/or-mwc-menu";
5
+ import {i18next} from "@openremote/or-translate";
6
+ import {ListItem} from "@openremote/or-mwc-components/or-mwc-list";
7
+ import {InputType, OrInputChangedEvent} from "@openremote/or-mwc-components/or-mwc-input";
8
+ import manager, {Util} from "@openremote/core";
9
+ import {when} from "lit/directives/when.js";
10
+ import {createRef, Ref, ref} from 'lit/directives/ref.js';
11
+ import {AssetTreeConfig, OrAssetTree} from "@openremote/or-asset-tree";
12
+ import {OrMwcDialog, showDialog} from "@openremote/or-mwc-components/or-mwc-dialog";
13
+
14
+ export class AssetTypeSelectEvent extends CustomEvent<string> {
15
+
16
+ public static readonly NAME = "assettype-select";
17
+
18
+ constructor(assetTypeName: string) {
19
+ super(AssetTypeSelectEvent.NAME, {
20
+ bubbles: true,
21
+ composed: true,
22
+ detail: assetTypeName
23
+ });
24
+ }
25
+ }
26
+
27
+ export class AssetIdsSelectEvent extends CustomEvent<string | string[]> {
28
+
29
+ public static readonly NAME = "assetids-select"
30
+
31
+ constructor(assetIds: string | string[]) {
32
+ super(AssetIdsSelectEvent.NAME, {
33
+ bubbles: true,
34
+ composed: true,
35
+ detail: assetIds
36
+ });
37
+ }
38
+ }
39
+
40
+ export class AttributeNamesSelectEvent extends CustomEvent<string | string[]> {
41
+
42
+ public static readonly NAME = "attributenames-select";
43
+
44
+ constructor(attributeNames: string | string[]) {
45
+ super(AttributeNamesSelectEvent.NAME, {
46
+ bubbles: true,
47
+ composed: true,
48
+ detail: attributeNames
49
+ });
50
+ }
51
+ }
52
+
53
+ export interface AssetTypesFilterConfig {
54
+ assets?: {
55
+ enabled?: boolean,
56
+ multi?: boolean
57
+ },
58
+ attributes?: {
59
+ enabled?: boolean,
60
+ multi?: boolean,
61
+ valueTypes?: string[]
62
+ }
63
+ }
64
+
65
+ const styling = css`
66
+ .switchMwcInputContainer {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ }
71
+ `
72
+
73
+ @customElement("assettypes-panel")
74
+ export class AssettypesPanel extends LitElement {
75
+
76
+ @property() // selected asset type
77
+ protected assetType?: string;
78
+
79
+ @property()
80
+ protected config: AssetTypesFilterConfig = {
81
+ attributes: {
82
+ enabled: true
83
+ }
84
+ }
85
+
86
+ @property() // IDs of assets; either undefined, a single entry, or multi select
87
+ protected assetIds: undefined | string | string[];
88
+
89
+ @property() // names of selected attributes; either undefined, a single entry, or multi select
90
+ protected attributeNames: undefined | string | string[];
91
+
92
+ /* ----------- */
93
+
94
+ @state()
95
+ protected _attributeSelectList: string[][] = [];
96
+
97
+ @state()
98
+ protected _loadedAssetTypes: AssetDescriptor[] = AssetModelUtil.getAssetDescriptors().filter((t) => t.descriptorType === "asset");
99
+
100
+
101
+ static get styles() {
102
+ return [styling];
103
+ }
104
+
105
+ protected willUpdate(changedProps: PropertyValues) {
106
+ super.willUpdate(changedProps);
107
+ if (changedProps.has("assetType") && this.assetType) {
108
+ this._attributeSelectList = this.getAttributesByType(this.assetType)!;
109
+ this.dispatchEvent(new AssetTypeSelectEvent(this.assetType));
110
+ }
111
+ if (changedProps.has("assetIds") && this.assetIds) {
112
+ this.dispatchEvent(new AssetIdsSelectEvent(this.assetIds));
113
+ }
114
+ if (changedProps.has("attributeNames") && this.attributeNames) {
115
+ this.dispatchEvent(new AttributeNamesSelectEvent(this.attributeNames));
116
+ }
117
+ }
118
+
119
+ protected render(): TemplateResult {
120
+ return html`
121
+ <div style="display: flex; flex-direction: column; gap: 8px;">
122
+
123
+ <!-- Select asset type -->
124
+ <div>
125
+ ${this._loadedAssetTypes.length > 0 ? getContentWithMenuTemplate(
126
+ this.getAssetTypeTemplate(),
127
+ this.mapDescriptors(this._loadedAssetTypes, {
128
+ text: i18next.t("filter.assetTypeMenuNone"),
129
+ value: "",
130
+ icon: "selection-ellipse"
131
+ }) as ListItem[],
132
+ undefined,
133
+ (v: string[] | string) => {
134
+ this.assetType = v as string;
135
+ },
136
+ undefined,
137
+ false,
138
+ true,
139
+ true,
140
+ true) : html``
141
+ }
142
+ </div>
143
+
144
+ <!-- Select one or more assets -->
145
+ ${when(this.config.assets?.enabled, () => {
146
+ const assetIds = (typeof this.assetIds === 'string') ? [this.assetIds] : this.assetIds;
147
+ return html`
148
+ <div>
149
+ <or-mwc-input .type="${InputType.BUTTON}" .label="${(this.assetIds?.length || 0) + ' ' + i18next.t('assets')}" .disabled="${!this.assetType}" fullWidth outlined comfortable style="width: 100%;"
150
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this._openAssetSelector(this.assetType!, assetIds, this.config.assets?.multi)}"
151
+ ></or-mwc-input>
152
+ </div>
153
+ `;
154
+ })}
155
+
156
+ <!-- Select one or more attributes -->
157
+ ${when(this.config.attributes?.enabled, () => {
158
+ const options: [string, string][] = this._attributeSelectList.map(al => [al[0], al[1]]);
159
+ const searchProvider: (search?: string) => Promise<[any, string][]> = async (search) => {
160
+ return search ? options.filter(o => o[1].toLowerCase().includes(search.toLowerCase())) : options;
161
+ };
162
+ return html`
163
+ <div>
164
+ <or-mwc-input .type="${InputType.SELECT}" label="${i18next.t("filter.attributeLabel")}" .disabled="${!this.assetType}" style="width: 100%;"
165
+ .options="${options}" .searchProvider="${searchProvider}" .multiple="${this.config.attributes?.multi}" .value="${this.attributeNames as string}"
166
+ @or-mwc-input-changed="${(ev: OrInputChangedEvent) => {
167
+ this.attributeNames = ev.detail.value;
168
+ }}"
169
+ ></or-mwc-input>
170
+ </div>
171
+ `
172
+
173
+ })}
174
+ </div>
175
+ `;
176
+ }
177
+
178
+
179
+ /* ----------- */
180
+
181
+ protected getAssetTypeTemplate(): TemplateResult {
182
+ if (this.assetType) {
183
+ const descriptor: AssetDescriptor | undefined = this._loadedAssetTypes.find((at: AssetDescriptor) => at.name === this.assetType);
184
+ if (descriptor) {
185
+ return this.getSelectedHeader(descriptor);
186
+ } else {
187
+ return this.getSelectHeader();
188
+ }
189
+ } else {
190
+ return this.getSelectHeader();
191
+ }
192
+ }
193
+
194
+ protected getSelectHeader(): TemplateResult {
195
+ return html`
196
+ <or-mwc-input style="width:100%;" type="${InputType.TEXT}" readonly .label="${i18next.t("filter.assetTypeLabel")}"
197
+ iconTrailing="menu-down" iconColor="rgba(0, 0, 0, 0.87)" icon="selection-ellipse"
198
+ value="${i18next.t("filter.assetTypeNone")}">
199
+ </or-mwc-input>
200
+ `;
201
+ }
202
+
203
+ protected getSelectedHeader(descriptor: AssetDescriptor): TemplateResult {
204
+ return html`
205
+ <or-mwc-input style="width:100%;" type="${InputType.TEXT}" readonly .label="${i18next.t("filter.assetTypeLabel")}"
206
+ .iconColor="${descriptor.colour}" iconTrailing="menu-down" icon="${descriptor.icon}"
207
+ value="${Util.getAssetTypeLabel(descriptor)}">
208
+ </or-mwc-input>
209
+ `;
210
+ }
211
+
212
+ protected mapDescriptors(descriptors: AssetDescriptor[], withNoneValue?: ListItem): ListItem[] {
213
+ const items: ListItem[] = descriptors.map((descriptor) => {
214
+ return {
215
+ styleMap: {
216
+ "--or-icon-fill": descriptor.colour ? "#" + descriptor.colour : "unset"
217
+ },
218
+ icon: descriptor.icon,
219
+ text: Util.getAssetTypeLabel(descriptor),
220
+ value: descriptor.name!,
221
+ data: descriptor
222
+ }
223
+ }).sort(Util.sortByString((listItem) => listItem.text));
224
+
225
+ if (withNoneValue) {
226
+ items.splice(0, 0, withNoneValue);
227
+ }
228
+ return items;
229
+ }
230
+
231
+ protected getAttributesByType(type: string) {
232
+ const descriptor: AssetDescriptor = (AssetModelUtil.getAssetDescriptor(type) as AssetDescriptor);
233
+ if (descriptor) {
234
+ const typeInfo: AssetTypeInfo = (AssetModelUtil.getAssetTypeInfo(descriptor) as AssetTypeInfo);
235
+ if (typeInfo?.attributeDescriptors) {
236
+ const valueTypes = this.config.attributes?.valueTypes;
237
+ const filtered = valueTypes ? typeInfo.attributeDescriptors.filter(ad => valueTypes.indexOf(ad.type!) > -1) : typeInfo.attributeDescriptors;
238
+ return filtered
239
+ .map((ad) => {
240
+ const label = Util.getAttributeLabel(ad, undefined, type, false);
241
+ return [ad.name!, label];
242
+ })
243
+ .sort(Util.sortByString((attr) => attr[1]));
244
+ }
245
+ }
246
+ }
247
+
248
+ protected _openAssetSelector(assetType: string, assetIds?: string[], multi = false) {
249
+ const assetTreeRef: Ref<OrAssetTree> = createRef();
250
+ const config = {
251
+ select: {
252
+ types: [assetType],
253
+ multiSelect: multi
254
+ }
255
+ } as AssetTreeConfig
256
+ const dialog = showDialog(new OrMwcDialog()
257
+ .setHeading(i18next.t("linkedAssets"))
258
+ .setContent(html`
259
+ <div style="width: 400px;">
260
+ <or-asset-tree ${ref(assetTreeRef)} .dataProvider="${this.assetTreeDataProvider}" expandAllNodes
261
+ id="chart-asset-tree" readonly .config="${config}" .selectedIds="${assetIds}"
262
+ .showSortBtn="${false}" .showFilter="${false}" .checkboxes="${multi}"
263
+ ></or-asset-tree>
264
+ </div>
265
+ `)
266
+ .setActions([
267
+ {
268
+ default: true,
269
+ actionName: "cancel",
270
+ content: "cancel",
271
+ },
272
+ {
273
+ actionName: "ok",
274
+ content: "ok",
275
+ action: () => {
276
+ const tree = assetTreeRef.value;
277
+ if(tree?.selectedIds) {
278
+ if(multi) {
279
+ this.assetIds = tree.selectedIds;
280
+ } else {
281
+ this.assetIds = tree.selectedIds[0];
282
+ }
283
+ }
284
+ }
285
+ }
286
+ ])
287
+ .setDismissAction({
288
+ actionName: "cancel",
289
+ }));
290
+ }
291
+
292
+ protected assetTreeDataProvider = async (): Promise<Asset[]> => {
293
+ const assetQuery: AssetQuery = {
294
+ realm: {
295
+ name: manager.displayRealm
296
+ },
297
+ select: { // Just need the basic asset info
298
+ attributes: []
299
+ }
300
+ };
301
+ // At first, just fetch all accessible assets without attribute info...
302
+ const assets = (await manager.rest.api.AssetResource.queryAssets(assetQuery)).data;
303
+
304
+ // After fetching, narrow down the list to assets with the same assetType.
305
+ // Since it is a tree, we also include the parents of those assets, based on the 'asset.path' variable.
306
+ const pathsOfAssetType = assets.filter(a => a.type === this.assetType).map(a => a.path!);
307
+ const filteredAssetIds = [...new Set([].concat(...pathsOfAssetType as any[]))] as string[];
308
+ return assets.filter(a => filteredAssetIds.includes(a.id!));
309
+ }
310
+
311
+ }
@@ -0,0 +1,304 @@
1
+ import {css, CSSResult, html, LitElement, PropertyValues, TemplateResult, unsafeCSS} from "lit";
2
+ import {customElement, property, state} from "lit/decorators.js";
3
+ import {Asset, AssetModelUtil, Attribute, AttributeRef} from "@openremote/model";
4
+ import {style} from "../style";
5
+ import {when} from "lit/directives/when.js";
6
+ import {map} from "lit/directives/map.js";
7
+ import {guard} from "lit/directives/guard.js";
8
+ import {i18next} from "@openremote/or-translate";
9
+ import "@openremote/or-translate";
10
+ import {InputType} from "@openremote/or-mwc-components/or-mwc-input";
11
+ import manager, {DefaultColor5, Util} from "@openremote/core";
12
+ import {getAssetDescriptorIconTemplate} from "@openremote/or-icon";
13
+ import {OrAttributePicker, OrAttributePickerPickedEvent} from "@openremote/or-attribute-picker";
14
+ import {showDialog} from "@openremote/or-mwc-components/or-mwc-dialog";
15
+ import {showSnackbar} from "@openremote/or-mwc-components/or-mwc-snackbar";
16
+
17
+ export interface AttributeAction {
18
+ icon: string,
19
+ tooltip: string,
20
+ disabled: boolean
21
+ }
22
+
23
+ export class AttributeActionEvent extends CustomEvent<{ asset: Asset, attributeRef: AttributeRef, action: AttributeAction }> {
24
+
25
+ public static readonly NAME = "attribute-action"
26
+
27
+ constructor(asset: Asset, attributeRef: AttributeRef, action: AttributeAction) {
28
+ super(AttributeActionEvent.NAME, {
29
+ bubbles: true,
30
+ composed: true,
31
+ detail: {
32
+ asset: asset,
33
+ attributeRef: attributeRef,
34
+ action: action
35
+ }
36
+ });
37
+ }
38
+ }
39
+
40
+ export class AttributesSelectEvent extends CustomEvent<{ assets: Asset[], attributeRefs: AttributeRef[] }> {
41
+
42
+ public static readonly NAME = "attribute-select";
43
+
44
+ constructor(assets: Asset[], attributeRefs: AttributeRef[]) {
45
+ super(AttributesSelectEvent.NAME, {
46
+ bubbles: true,
47
+ composed: true,
48
+ detail: {
49
+ assets: assets,
50
+ attributeRefs: attributeRefs
51
+ }
52
+ });
53
+ }
54
+ }
55
+
56
+ const styling = css`
57
+ #attribute-list {
58
+ overflow: auto;
59
+ flex: 1 1 0;
60
+ width: 100%;
61
+ display: flex;
62
+ flex-direction: column;
63
+ }
64
+
65
+ .attribute-list-item {
66
+ position: relative;
67
+ cursor: pointer;
68
+ display: flex;
69
+ flex-direction: row;
70
+ align-items: stretch;
71
+ gap: 10px;
72
+ padding: 0;
73
+ min-height: 50px;
74
+ }
75
+
76
+ .attribute-list-item-icon {
77
+ display: flex;
78
+ align-items: center;
79
+ --or-icon-width: 20px;
80
+ }
81
+
82
+ .attribute-list-item-label {
83
+ display: flex;
84
+ justify-content: center;
85
+ flex: 1 1 0;
86
+ line-height: 16px;
87
+ flex-direction: column;
88
+ }
89
+
90
+ .attribute-list-item-actions {
91
+ flex: 1;
92
+ justify-content: end;
93
+ align-items: center;
94
+ display: flex;
95
+ gap: 8px;
96
+ }
97
+
98
+ .attribute-list-item-bullet {
99
+ width: 14px;
100
+ height: 14px;
101
+ border-radius: 7px;
102
+ margin-right: 10px;
103
+ }
104
+
105
+ .attribute-list-item .button.delete {
106
+ display: none;
107
+ }
108
+
109
+ .attribute-list-item:hover .button.delete {
110
+ display: block;
111
+ }
112
+
113
+ .button-action {
114
+ background: none;
115
+ visibility: hidden;
116
+ color: var(--or-app-color5, ${unsafeCSS(DefaultColor5)});
117
+ --or-icon-fill: var(--or-app-color5, ${unsafeCSS(DefaultColor5)});
118
+ display: inline-block;
119
+ border: none;
120
+ padding: 0;
121
+ cursor: pointer;
122
+ }
123
+
124
+ .attribute-list-item:hover .attribute-list-item-actions {
125
+ background: white;
126
+ z-index: 1;
127
+ }
128
+
129
+ .attribute-list-item:hover .button-action {
130
+ visibility: visible;
131
+ }
132
+
133
+ .button-action:hover {
134
+ --or-icon-fill: var(--or-app-color4);
135
+ }
136
+ `
137
+
138
+ @customElement('attributes-panel')
139
+ export class AttributesPanel extends LitElement {
140
+
141
+ @property()
142
+ protected attributeRefs: AttributeRef[] = [];
143
+
144
+ @property()
145
+ protected multi: boolean = false;
146
+
147
+ @property()
148
+ protected onlyDataAttrs: boolean = false;
149
+
150
+ @property()
151
+ protected attributeFilter?: (attribute: Attribute<any>) => boolean;
152
+
153
+ @property()
154
+ protected attributeLabelCallback?: (asset: Asset, attribute: Attribute<any>, attributeLabel: string) => TemplateResult;
155
+
156
+ @property()
157
+ protected attributeActionCallback?: (attribute: AttributeRef) => AttributeAction[]
158
+
159
+ @state()
160
+ protected loadedAssets: Asset[] = [];
161
+
162
+ static get styles(): CSSResult[] {
163
+ return [styling, style];
164
+ }
165
+
166
+ // Lit lifecycle method to compute values during update
167
+ protected willUpdate(changedProps: PropertyValues) {
168
+ super.willUpdate(changedProps);
169
+
170
+ if (!this.attributeRefs) {
171
+ this.attributeRefs = [];
172
+ }
173
+ if (changedProps.has("attributeRefs") && this.attributeRefs) {
174
+ this.loadAssets().then((assets) => {
175
+
176
+ // Only dispatch event when it CHANGED, so not from 'undefined' to [];
177
+ if(changedProps.get("attributeRefs")) {
178
+ this.dispatchEvent(new AttributesSelectEvent(assets, this.attributeRefs))
179
+ }
180
+
181
+ })
182
+ }
183
+ }
184
+
185
+ protected getLoadedAsset(attrRef: AttributeRef): Asset | undefined {
186
+ return this.loadedAssets?.find((asset) => asset.id === attrRef.id)
187
+ }
188
+
189
+ protected removeWidgetAttribute(attributeRef: AttributeRef) {
190
+ if (this.attributeRefs != null) {
191
+ this.attributeRefs = this.attributeRefs.filter(ar => ar !== attributeRef);
192
+ }
193
+ }
194
+
195
+ protected async loadAssets(): Promise<Asset[]> {
196
+ if(this.attributeRefs.filter(ar => !this.getLoadedAsset(ar)).length > 0) {
197
+ const assets = await this.fetchAssets(this.attributeRefs);
198
+ this.loadedAssets = assets;
199
+ return assets;
200
+ } else {
201
+ return this.loadedAssets;
202
+ }
203
+ }
204
+
205
+ // Fetching the assets according to the AttributeRef[] input in DashboardWidget if required.
206
+ // TODO: Move this to more generic spot?
207
+ async fetchAssets(attributeRefs: AttributeRef[] = []): Promise<Asset[]> {
208
+ let assets: Asset[] = [];
209
+ await manager.rest.api.AssetResource.queryAssets({
210
+ ids: attributeRefs.map((x: AttributeRef) => x.id) as string[],
211
+ realm: { name: manager.displayRealm },
212
+ select: {
213
+ attributes: attributeRefs.map((x: AttributeRef) => x.name) as string[]
214
+ }
215
+ }).then(response => {
216
+ assets = response.data;
217
+ }).catch((reason) => {
218
+ console.error(reason);
219
+ showSnackbar(undefined, "errorOccurred");
220
+ });
221
+ return assets;
222
+ }
223
+
224
+ protected onAttributeActionClick(asset: Asset, attributeRef: AttributeRef, action: AttributeAction) {
225
+ this.dispatchEvent(new AttributeActionEvent(asset, attributeRef, action));
226
+ }
227
+
228
+ protected openAttributeSelector(attributeRefs: AttributeRef[], multi: boolean, onlyDataAttrs = true, attributeFilter?: (attribute: Attribute<any>) => boolean) {
229
+ let dialog: OrAttributePicker;
230
+ if (attributeRefs != null) {
231
+ dialog = showDialog(new OrAttributePicker().setMultiSelect(multi).setSelectedAttributes(attributeRefs).setShowOnlyDatapointAttrs(onlyDataAttrs).setAttributeFilter(attributeFilter));
232
+ } else {
233
+ dialog = showDialog(new OrAttributePicker().setMultiSelect(multi).setShowOnlyDatapointAttrs(onlyDataAttrs))
234
+ }
235
+ dialog.addEventListener(OrAttributePickerPickedEvent.NAME, (event: CustomEvent) => {
236
+ this.attributeRefs = event.detail;
237
+ })
238
+ }
239
+
240
+ protected render(): TemplateResult {
241
+ return html`
242
+ <div>
243
+ ${when(this.attributeRefs.length > 0, () => html`
244
+
245
+ <div id="attribute-list">
246
+ ${guard([this.attributeRefs, this.loadedAssets, this.attributeActionCallback, this.attributeLabelCallback], () => html`
247
+ ${map(this.attributeRefs, (attributeRef: AttributeRef) => {
248
+ const asset = this.getLoadedAsset(attributeRef);
249
+ if (asset) {
250
+ const attribute = asset.attributes![attributeRef.name!];
251
+ const descriptors = AssetModelUtil.getAttributeAndValueDescriptors(asset.type, attributeRef.name, attribute);
252
+ const label = Util.getAttributeLabel(attribute, descriptors[0], asset.type, true);
253
+ return html`
254
+ <div class="attribute-list-item">
255
+ <div class="attribute-list-item-icon">
256
+ <span>${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(asset.type))}</span>
257
+ </div>
258
+ <div class="attribute-list-item-label">
259
+ ${when(!!this.attributeLabelCallback,
260
+ () => this.attributeLabelCallback!(asset, attribute, label),
261
+ () => html`
262
+ <span>${asset.name}</span>
263
+ <span style="font-size:14px; color:grey;">${label}</span>
264
+ `
265
+ )}
266
+ </div>
267
+ <div class="attribute-list-item-actions">
268
+
269
+ <!-- Custom actions defined by callback -->
270
+ ${when(!!this.attributeActionCallback, () => {
271
+ return this.attributeActionCallback!(attributeRef).map((action) => html`
272
+ <button class="button-action" .disabled="${action.disabled}" title="${action.tooltip}" @click="${() => this.onAttributeActionClick(asset, attributeRef, action)}">
273
+ <or-icon icon="${action.icon}"></or-icon>
274
+ </button>
275
+ `);
276
+ })}
277
+ <!-- Remove attribute button -->
278
+ <button class="button-action" title="${i18next.t('delete')}" @click="${() => this.removeWidgetAttribute(attributeRef)}">
279
+ <or-icon icon="close-circle"></or-icon>
280
+ </button>
281
+ </div>
282
+ </div>
283
+ `;
284
+ } else {
285
+ return undefined;
286
+ }
287
+ })}
288
+ `)}
289
+ </div>
290
+
291
+ `, () => html`
292
+ <span style="padding: 14px 0; display: block;"><or-translate value="noAttributesConnected"></or-translate></span>
293
+ `)}
294
+
295
+ <!-- Button that opens attribute selection -->
296
+ <or-mwc-input .type="${InputType.BUTTON}" label="attribute" icon="${(this.multi || this.attributeRefs.length === 0) ? "plus" : "swap-horizontal"}"
297
+ style="margin-top: 8px;"
298
+ @or-mwc-input-changed="${() => this.openAttributeSelector(this.attributeRefs, this.multi, this.onlyDataAttrs, this.attributeFilter)}">
299
+ </or-mwc-input>
300
+ </div>
301
+ `;
302
+ }
303
+
304
+ }