@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.
- package/README.md +156 -0
- package/build.gradle +15 -0
- package/lib/controls/dashboard-refresh-controls.d.ts +17 -0
- package/lib/controls/dashboard-refresh-controls.js +17 -0
- package/lib/controls/dashboard-refresh-controls.js.map +1 -0
- package/lib/index.d.ts +71 -0
- package/lib/index.js +285 -0
- package/lib/index.js.map +1 -0
- package/lib/or-dashboard-boardsettings.d.ts +19 -0
- package/lib/or-dashboard-boardsettings.js +121 -0
- package/lib/or-dashboard-boardsettings.js.map +1 -0
- package/lib/or-dashboard-browser.d.ts +9 -0
- package/lib/or-dashboard-browser.js +35 -0
- package/lib/or-dashboard-browser.js.map +1 -0
- package/lib/or-dashboard-engine.d.ts +4 -0
- package/lib/or-dashboard-engine.js +1 -0
- package/lib/or-dashboard-engine.js.map +1 -0
- package/lib/or-dashboard-keyhandler.d.ts +6 -0
- package/lib/or-dashboard-keyhandler.js +1 -0
- package/lib/or-dashboard-keyhandler.js.map +1 -0
- package/lib/or-dashboard-preview.d.ts +70 -0
- package/lib/or-dashboard-preview.js +155 -0
- package/lib/or-dashboard-preview.js.map +1 -0
- package/lib/or-dashboard-tree.d.ts +25 -0
- package/lib/or-dashboard-tree.js +62 -0
- package/lib/or-dashboard-tree.js.map +1 -0
- package/lib/or-dashboard-widgetcontainer.d.ts +23 -0
- package/lib/or-dashboard-widgetcontainer.js +20 -0
- package/lib/or-dashboard-widgetcontainer.js.map +1 -0
- package/lib/or-dashboard-widgetsettings.d.ts +15 -0
- package/lib/or-dashboard-widgetsettings.js +16 -0
- package/lib/or-dashboard-widgetsettings.js.map +1 -0
- package/lib/panels/assettypes-panel.d.ts +44 -0
- package/lib/panels/assettypes-panel.js +51 -0
- package/lib/panels/assettypes-panel.js.map +1 -0
- package/lib/panels/attributes-panel.d.ts +41 -0
- package/lib/panels/attributes-panel.js +126 -0
- package/lib/panels/attributes-panel.js.map +1 -0
- package/lib/panels/thresholds-panel.d.ts +39 -0
- package/lib/panels/thresholds-panel.js +129 -0
- package/lib/panels/thresholds-panel.js.map +1 -0
- package/lib/service/dashboard-service.d.ts +13 -0
- package/lib/service/dashboard-service.js +1 -0
- package/lib/service/dashboard-service.js.map +1 -0
- package/lib/service/widget-service.d.ts +8 -0
- package/lib/service/widget-service.js +1 -0
- package/lib/service/widget-service.js.map +1 -0
- package/lib/settings/attribute-input-settings.d.ts +13 -0
- package/lib/settings/attribute-input-settings.js +36 -0
- package/lib/settings/attribute-input-settings.js.map +1 -0
- package/lib/settings/chart-settings.d.ts +30 -0
- package/lib/settings/chart-settings.js +144 -0
- package/lib/settings/chart-settings.js.map +1 -0
- package/lib/settings/gauge-settings.d.ts +15 -0
- package/lib/settings/gauge-settings.js +37 -0
- package/lib/settings/gauge-settings.js.map +1 -0
- package/lib/settings/image-settings.d.ts +17 -0
- package/lib/settings/image-settings.js +52 -0
- package/lib/settings/image-settings.js.map +1 -0
- package/lib/settings/kpi-settings.d.ts +15 -0
- package/lib/settings/kpi-settings.js +47 -0
- package/lib/settings/kpi-settings.js.map +1 -0
- package/lib/settings/map-settings.d.ts +21 -0
- package/lib/settings/map-settings.js +72 -0
- package/lib/settings/map-settings.js.map +1 -0
- package/lib/settings/table-settings.d.ts +14 -0
- package/lib/settings/table-settings.js +33 -0
- package/lib/settings/table-settings.js.map +1 -0
- package/lib/style.d.ts +1 -0
- package/lib/style.js +99 -0
- package/lib/style.js.map +1 -0
- package/lib/util/or-asset-widget.d.ts +20 -0
- package/lib/util/or-asset-widget.js +1 -0
- package/lib/util/or-asset-widget.js.map +1 -0
- package/lib/util/or-widget.d.ts +31 -0
- package/lib/util/or-widget.js +1 -0
- package/lib/util/or-widget.js.map +1 -0
- package/lib/util/settings-panel.d.ts +9 -0
- package/lib/util/settings-panel.js +56 -0
- package/lib/util/settings-panel.js.map +1 -0
- package/lib/util/widget-config.d.ts +2 -0
- package/lib/util/widget-config.js +1 -0
- package/lib/util/widget-config.js.map +1 -0
- package/lib/util/widget-settings.d.ts +23 -0
- package/lib/util/widget-settings.js +1 -0
- package/lib/util/widget-settings.js.map +1 -0
- package/lib/widgets/attribute-input-widget.d.ts +24 -0
- package/lib/widgets/attribute-input-widget.js +31 -0
- package/lib/widgets/attribute-input-widget.js.map +1 -0
- package/lib/widgets/chart-widget.d.ts +25 -0
- package/lib/widgets/chart-widget.js +15 -0
- package/lib/widgets/chart-widget.js.map +1 -0
- package/lib/widgets/gauge-widget.d.ts +22 -0
- package/lib/widgets/gauge-widget.js +12 -0
- package/lib/widgets/gauge-widget.js.map +1 -0
- package/lib/widgets/image-widget.d.ts +25 -0
- package/lib/widgets/image-widget.js +54 -0
- package/lib/widgets/image-widget.js.map +1 -0
- package/lib/widgets/kpi-widget.d.ts +21 -0
- package/lib/widgets/kpi-widget.js +8 -0
- package/lib/widgets/kpi-widget.js.map +1 -0
- package/lib/widgets/map-widget.d.ts +39 -0
- package/lib/widgets/map-widget.js +9 -0
- package/lib/widgets/map-widget.js.map +1 -0
- package/lib/widgets/table-widget.d.ts +25 -0
- package/lib/widgets/table-widget.js +12 -0
- package/lib/widgets/table-widget.js.map +1 -0
- package/package.json +32 -0
- package/src/controls/dashboard-refresh-controls.ts +100 -0
- package/src/index.ts +731 -0
- package/src/or-dashboard-boardsettings.ts +249 -0
- package/src/or-dashboard-browser.ts +160 -0
- package/src/or-dashboard-engine.ts +17 -0
- package/src/or-dashboard-keyhandler.ts +25 -0
- package/src/or-dashboard-preview.ts +713 -0
- package/src/or-dashboard-tree.ts +304 -0
- package/src/or-dashboard-widgetcontainer.ts +155 -0
- package/src/or-dashboard-widgetsettings.ts +91 -0
- package/src/panels/assettypes-panel.ts +311 -0
- package/src/panels/attributes-panel.ts +304 -0
- package/src/panels/thresholds-panel.ts +285 -0
- package/src/service/dashboard-service.ts +89 -0
- package/src/service/widget-service.ts +48 -0
- package/src/settings/attribute-input-settings.ts +79 -0
- package/src/settings/chart-settings.ts +306 -0
- package/src/settings/gauge-settings.ts +93 -0
- package/src/settings/image-settings.ts +175 -0
- package/src/settings/kpi-settings.ts +106 -0
- package/src/settings/map-settings.ts +185 -0
- package/src/settings/table-settings.ts +92 -0
- package/src/style.ts +104 -0
- package/src/util/or-asset-widget.ts +110 -0
- package/src/util/or-widget.ts +60 -0
- package/src/util/settings-panel.ts +93 -0
- package/src/util/widget-config.ts +2 -0
- package/src/util/widget-settings.ts +58 -0
- package/src/widgets/attribute-input-widget.ts +143 -0
- package/src/widgets/chart-widget.ts +203 -0
- package/src/widgets/gauge-widget.ts +111 -0
- package/src/widgets/image-widget.ts +180 -0
- package/src/widgets/kpi-widget.ts +97 -0
- package/src/widgets/map-widget.ts +187 -0
- package/src/widgets/table-widget.ts +157 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
- 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
|
+
}
|