@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,180 @@
|
|
|
1
|
+
import {WidgetConfig} from "../util/widget-config";
|
|
2
|
+
import {AssetModelUtil, Attribute, AttributeRef, WellknownValueTypes} from "@openremote/model";
|
|
3
|
+
import {OrWidget, WidgetManifest} from "../util/or-widget";
|
|
4
|
+
import { customElement } from "lit/decorators.js";
|
|
5
|
+
import {WidgetSettings} from "../util/widget-settings";
|
|
6
|
+
import {css, CSSResult, html, PropertyValues, TemplateResult, unsafeCSS } from "lit";
|
|
7
|
+
import {OrAssetWidget} from "../util/or-asset-widget";
|
|
8
|
+
import {ImageSettings} from "../settings/image-settings";
|
|
9
|
+
import { when } from "lit/directives/when.js";
|
|
10
|
+
import {DefaultColor2, DefaultColor3, Util} from "@openremote/core";
|
|
11
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
12
|
+
|
|
13
|
+
const styling = css`
|
|
14
|
+
#img-wrapper {
|
|
15
|
+
height: 100%;
|
|
16
|
+
width: 100%;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
align-items: center;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
z-index: 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#img-container {
|
|
26
|
+
position: relative;
|
|
27
|
+
max-height: 100%;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#img-content {
|
|
31
|
+
height: 100%;
|
|
32
|
+
max-height: 100%;
|
|
33
|
+
max-width: 100%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#overlay {
|
|
37
|
+
position: absolute;
|
|
38
|
+
z-index: 3;
|
|
39
|
+
|
|
40
|
+
/* additional marker styling */
|
|
41
|
+
color: var(--or-app-color2, ${unsafeCSS(DefaultColor2)});
|
|
42
|
+
background-color: var(--or-app-color3, ${unsafeCSS(DefaultColor3)});
|
|
43
|
+
border-radius: 15px;
|
|
44
|
+
padding: 3px 8px 5px 8px;
|
|
45
|
+
object-fit: contain;
|
|
46
|
+
text-overflow: ellipsis;
|
|
47
|
+
white-space: nowrap;
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
export interface ImageAssetMarker {
|
|
52
|
+
attributeRef: AttributeRef,
|
|
53
|
+
coordinates: [number, number]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ImageWidgetConfig extends WidgetConfig {
|
|
57
|
+
attributeRefs: AttributeRef[];
|
|
58
|
+
markers: ImageAssetMarker[];
|
|
59
|
+
showTimestampControls: boolean;
|
|
60
|
+
imagePath: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getDefaultWidgetConfig(): ImageWidgetConfig {
|
|
64
|
+
return {
|
|
65
|
+
attributeRefs: [],
|
|
66
|
+
showTimestampControls: false,
|
|
67
|
+
imagePath: '',
|
|
68
|
+
markers: [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@customElement("image-widget")
|
|
73
|
+
export class ImageWidget extends OrAssetWidget {
|
|
74
|
+
|
|
75
|
+
// Override of widgetConfig with extended type
|
|
76
|
+
protected readonly widgetConfig!: ImageWidgetConfig;
|
|
77
|
+
|
|
78
|
+
static getManifest(): WidgetManifest {
|
|
79
|
+
return {
|
|
80
|
+
displayName: "Image",
|
|
81
|
+
displayIcon: "file-image-marker",
|
|
82
|
+
minColumnWidth: 1,
|
|
83
|
+
minColumnHeight: 1,
|
|
84
|
+
getContentHtml(config: ImageWidgetConfig): OrWidget {
|
|
85
|
+
return new ImageWidget(config);
|
|
86
|
+
},
|
|
87
|
+
getSettingsHtml(config: ImageWidgetConfig): WidgetSettings {
|
|
88
|
+
return new ImageSettings(config);
|
|
89
|
+
},
|
|
90
|
+
getDefaultConfig(): ImageWidgetConfig {
|
|
91
|
+
return getDefaultWidgetConfig();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public refreshContent(force: boolean) {
|
|
97
|
+
this.loadAssets();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static get styles(): CSSResult[] {
|
|
101
|
+
return [...super.styles, styling];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
updated(changedProps: PropertyValues) {
|
|
105
|
+
|
|
106
|
+
if(changedProps.has('widgetConfig') && this.widgetConfig) {
|
|
107
|
+
const attributeRefs = this.widgetConfig.attributeRefs;
|
|
108
|
+
const missingAssets = attributeRefs?.filter((attrRef: AttributeRef) => !this.isAttributeRefLoaded(attrRef));
|
|
109
|
+
if (missingAssets.length > 0) {
|
|
110
|
+
this.loadAssets();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
protected loadAssets() {
|
|
116
|
+
this.fetchAssets(this.widgetConfig.attributeRefs).then(assets => {
|
|
117
|
+
this.loadedAssets = assets!;
|
|
118
|
+
this.assetAttributes = this.widgetConfig.attributeRefs.map((attrRef: AttributeRef) => {
|
|
119
|
+
const assetIndex = assets!.findIndex(asset => asset.id === attrRef.id);
|
|
120
|
+
const foundAsset = assetIndex >= 0 ? assets![assetIndex] : undefined;
|
|
121
|
+
return foundAsset && foundAsset.attributes ? [assetIndex, foundAsset.attributes[attrRef.name!]] : undefined;
|
|
122
|
+
}).filter((indexAndAttr: any) => !!indexAndAttr) as [number, Attribute<any>][];
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// method to render and update the markers on the image
|
|
127
|
+
protected handleMarkerPlacement(config: ImageWidgetConfig) {
|
|
128
|
+
if (this.assetAttributes.length && config.attributeRefs.length > 0) {
|
|
129
|
+
|
|
130
|
+
if(config.markers.length === 0) {
|
|
131
|
+
console.error("No markers found!");
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
return config.attributeRefs.map((attributeRef, index) => {
|
|
135
|
+
const marker = config.markers.find(m => m.attributeRef.id === attributeRef.id && m.attributeRef.name === attributeRef.name);
|
|
136
|
+
const asset = this.loadedAssets.find(a => a.id === attributeRef.id);
|
|
137
|
+
let value: string | undefined;
|
|
138
|
+
const styles: any = {
|
|
139
|
+
"left": `${marker!.coordinates[0]}%`,
|
|
140
|
+
"top": `${marker!.coordinates[1]}%`
|
|
141
|
+
};
|
|
142
|
+
if(asset) {
|
|
143
|
+
const attribute = asset.attributes![attributeRef.name!];
|
|
144
|
+
const descriptors = AssetModelUtil.getAttributeAndValueDescriptors(asset.type, attributeRef.name, attribute);
|
|
145
|
+
value = Util.getAttributeValueAsString(attribute, descriptors[0], asset.type, true, "-");
|
|
146
|
+
if(attribute?.type === WellknownValueTypes.COLOURRGB && value !== "-") {
|
|
147
|
+
styles.backgroundColor = value;
|
|
148
|
+
styles.minHeight = "21px";
|
|
149
|
+
styles.minWidth = "13px";
|
|
150
|
+
value = undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return html`
|
|
154
|
+
<span id="overlay" style="${styleMap(styles)}">
|
|
155
|
+
${value}
|
|
156
|
+
</span>
|
|
157
|
+
`;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected render(): TemplateResult {
|
|
163
|
+
const imagePath = this.widgetConfig.imagePath;
|
|
164
|
+
return html`
|
|
165
|
+
<div id="img-wrapper">
|
|
166
|
+
${when(imagePath, () => html`
|
|
167
|
+
<div id="img-container">
|
|
168
|
+
<img id="img-content" src="${imagePath}" alt=""/>
|
|
169
|
+
<div>
|
|
170
|
+
${this.handleMarkerPlacement(this.widgetConfig)}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
`, () => html`
|
|
174
|
+
<span><or-translate value="dashboard.noImageSelected"></or-translate></span>
|
|
175
|
+
`)}
|
|
176
|
+
</div>
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { customElement } from "lit/decorators.js";
|
|
2
|
+
import {OrAssetWidget} from "../util/or-asset-widget";
|
|
3
|
+
import {OrWidget, WidgetManifest} from "../util/or-widget";
|
|
4
|
+
import {WidgetSettings} from "../util/widget-settings";
|
|
5
|
+
import {WidgetConfig} from "../util/widget-config";
|
|
6
|
+
import {Attribute, AttributeRef} from "@openremote/model";
|
|
7
|
+
import {html, TemplateResult } from "lit";
|
|
8
|
+
import {KpiSettings} from "../settings/kpi-settings";
|
|
9
|
+
import "@openremote/or-attribute-card";
|
|
10
|
+
|
|
11
|
+
export interface KpiWidgetConfig extends WidgetConfig {
|
|
12
|
+
attributeRefs: AttributeRef[];
|
|
13
|
+
period?: 'year' | 'month' | 'week' | 'day' | 'hour';
|
|
14
|
+
decimals: number;
|
|
15
|
+
deltaFormat: "absolute" | "percentage";
|
|
16
|
+
showTimestampControls: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getDefaultWidgetConfig(): KpiWidgetConfig {
|
|
20
|
+
return {
|
|
21
|
+
attributeRefs: [],
|
|
22
|
+
period: "day",
|
|
23
|
+
decimals: 0,
|
|
24
|
+
deltaFormat: "absolute",
|
|
25
|
+
showTimestampControls: false
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@customElement("kpi-widget")
|
|
30
|
+
export class KpiWidget extends OrAssetWidget {
|
|
31
|
+
|
|
32
|
+
protected widgetConfig!: KpiWidgetConfig;
|
|
33
|
+
|
|
34
|
+
static getManifest(): WidgetManifest {
|
|
35
|
+
return {
|
|
36
|
+
displayName: "KPI",
|
|
37
|
+
displayIcon: "label",
|
|
38
|
+
minColumnWidth: 1,
|
|
39
|
+
minColumnHeight: 1,
|
|
40
|
+
getContentHtml(config: KpiWidgetConfig): OrWidget {
|
|
41
|
+
return new KpiWidget(config);
|
|
42
|
+
},
|
|
43
|
+
getSettingsHtml(config: KpiWidgetConfig): WidgetSettings {
|
|
44
|
+
return new KpiSettings(config);
|
|
45
|
+
},
|
|
46
|
+
getDefaultConfig(): KpiWidgetConfig {
|
|
47
|
+
return getDefaultWidgetConfig();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
refreshContent(force: boolean): void {
|
|
53
|
+
this.loadAssets(this.widgetConfig.attributeRefs);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected updated(changedProps: Map<string, any>) {
|
|
57
|
+
super.updated(changedProps);
|
|
58
|
+
|
|
59
|
+
// If widgetConfig, and the attributeRefs of them have changed...
|
|
60
|
+
if(changedProps.has("widgetConfig") && this.widgetConfig) {
|
|
61
|
+
const attributeRefs = this.widgetConfig.attributeRefs;
|
|
62
|
+
|
|
63
|
+
// Check if list of attributes has changed, based on the cached assets
|
|
64
|
+
const loadedRefs: AttributeRef[] = attributeRefs?.filter((attrRef: AttributeRef) => this.isAttributeRefLoaded(attrRef));
|
|
65
|
+
if (loadedRefs?.length !== (attributeRefs ? attributeRefs.length : 0)) {
|
|
66
|
+
|
|
67
|
+
// Fetch the new list of assets
|
|
68
|
+
this.loadAssets(attributeRefs);
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return super.updated(changedProps);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected loadAssets(attributeRefs: AttributeRef[]) {
|
|
76
|
+
this.fetchAssets(attributeRefs).then((assets) => {
|
|
77
|
+
this.loadedAssets = assets;
|
|
78
|
+
this.assetAttributes = attributeRefs?.map((attrRef: AttributeRef) => {
|
|
79
|
+
const assetIndex = assets.findIndex((asset) => asset.id === attrRef.id);
|
|
80
|
+
const foundAsset = assetIndex >= 0 ? assets[assetIndex] : undefined;
|
|
81
|
+
return foundAsset && foundAsset.attributes ? [assetIndex, foundAsset.attributes[attrRef.name!]] : undefined;
|
|
82
|
+
}).filter((indexAndAttr: any) => !!indexAndAttr) as [number, Attribute<any>][];
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected render(): TemplateResult {
|
|
87
|
+
return html`
|
|
88
|
+
<div style="height: 100%; overflow: hidden;">
|
|
89
|
+
<or-attribute-card .assets="${this.loadedAssets}" .assetAttributes="${this.assetAttributes}" .period="${this.widgetConfig.period}"
|
|
90
|
+
.deltaFormat="${this.widgetConfig.deltaFormat}" .mainValueDecimals="${this.widgetConfig.decimals}"
|
|
91
|
+
showControls="${this.widgetConfig?.showTimestampControls}" showTitle="${false}" style="height: 100%;">
|
|
92
|
+
</or-attribute-card>
|
|
93
|
+
</div>
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {html, PropertyValues, TemplateResult} from "lit";
|
|
2
|
+
import {customElement} from "lit/decorators.js";
|
|
3
|
+
import {OrAssetWidget} from "../util/or-asset-widget";
|
|
4
|
+
import {OrWidget, WidgetManifest} from "../util/or-widget";
|
|
5
|
+
import {WidgetSettings} from "../util/widget-settings";
|
|
6
|
+
import {MapSettings} from "../settings/map-settings";
|
|
7
|
+
import {WidgetConfig} from "../util/widget-config";
|
|
8
|
+
import {Asset, AssetDescriptor, Attribute, AttributeRef, GeoJSONPoint, WellknownAttributes, WellknownMetaItems} from "@openremote/model";
|
|
9
|
+
import {
|
|
10
|
+
LngLatLike,
|
|
11
|
+
AttributeMarkerColours,
|
|
12
|
+
RangeAttributeMarkerColours,
|
|
13
|
+
AttributeMarkerColoursRange,
|
|
14
|
+
MapMarkerColours, MapMarkerAssetConfig,
|
|
15
|
+
} from "@openremote/or-map";
|
|
16
|
+
import {when} from "lit/directives/when.js";
|
|
17
|
+
import manager, {Util} from "@openremote/core";
|
|
18
|
+
import { showSnackbar } from "@openremote/or-mwc-components/or-mwc-snackbar";
|
|
19
|
+
import "@openremote/or-map";
|
|
20
|
+
|
|
21
|
+
export interface MapWidgetConfig extends WidgetConfig {
|
|
22
|
+
// General values
|
|
23
|
+
attributeRefs: AttributeRef[];
|
|
24
|
+
// Map related values
|
|
25
|
+
zoom?: number,
|
|
26
|
+
center?: LngLatLike,
|
|
27
|
+
lat?: number,
|
|
28
|
+
lng?: number,
|
|
29
|
+
// Map marker related values
|
|
30
|
+
showLabels: boolean,
|
|
31
|
+
showUnits: boolean,
|
|
32
|
+
showGeoJson: boolean,
|
|
33
|
+
boolColors: MapMarkerColours,
|
|
34
|
+
textColors: [string, string][],
|
|
35
|
+
// Threshold related values
|
|
36
|
+
thresholds: [number, string][],
|
|
37
|
+
min?: number,
|
|
38
|
+
max?: number,
|
|
39
|
+
// Asset type related values
|
|
40
|
+
assetType?: string,
|
|
41
|
+
valueType?: string,
|
|
42
|
+
attributeName?: string,
|
|
43
|
+
assetTypes: AssetDescriptor[],
|
|
44
|
+
assetIds: string[],
|
|
45
|
+
attributes: string[],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getDefaultWidgetConfig(): MapWidgetConfig {
|
|
49
|
+
return {
|
|
50
|
+
attributeRefs: [],
|
|
51
|
+
showLabels: false,
|
|
52
|
+
showUnits: false,
|
|
53
|
+
showGeoJson: true,
|
|
54
|
+
boolColors: {type: 'boolean', 'false': '#ef5350', 'true': '#4caf50'},
|
|
55
|
+
textColors: [['example', '4caf50'], ['example2', 'ef5350']],
|
|
56
|
+
thresholds: [[0, "#4caf50"], [75, "#ff9800"], [90, "#ef5350"]],
|
|
57
|
+
assetTypes: [],
|
|
58
|
+
assetType: undefined,
|
|
59
|
+
assetIds: [],
|
|
60
|
+
attributes: [],
|
|
61
|
+
} as MapWidgetConfig;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@customElement("map-widget")
|
|
65
|
+
export class MapWidget extends OrAssetWidget {
|
|
66
|
+
|
|
67
|
+
protected widgetConfig!: MapWidgetConfig
|
|
68
|
+
|
|
69
|
+
private markers: MapMarkerAssetConfig = {};
|
|
70
|
+
|
|
71
|
+
static getManifest(): WidgetManifest {
|
|
72
|
+
return {
|
|
73
|
+
displayName: "Map",
|
|
74
|
+
displayIcon: "map",
|
|
75
|
+
minColumnWidth: 2,
|
|
76
|
+
minColumnHeight: 2,
|
|
77
|
+
getContentHtml(config: MapWidgetConfig): OrWidget {
|
|
78
|
+
return new MapWidget(config);
|
|
79
|
+
},
|
|
80
|
+
getSettingsHtml(config: MapWidgetConfig): WidgetSettings {
|
|
81
|
+
return new MapSettings(config);
|
|
82
|
+
},
|
|
83
|
+
getDefaultConfig(): MapWidgetConfig {
|
|
84
|
+
return getDefaultWidgetConfig();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
refreshContent(force: boolean): void {
|
|
90
|
+
this.loadAssets();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protected updated(changedProps: PropertyValues) {
|
|
94
|
+
if(changedProps.has('widgetConfig') && this.widgetConfig) {
|
|
95
|
+
this.loadAssets();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected async loadAssets() {
|
|
100
|
+
if(this.widgetConfig.assetType && this.widgetConfig.attributeName) {
|
|
101
|
+
this.fetchAssetsByType([this.widgetConfig.assetType], this.widgetConfig.attributeName).then((assets) => {
|
|
102
|
+
this.loadedAssets = assets;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected async fetchAssetsByType(assetTypes: string[], attributeName: string) {
|
|
108
|
+
let assets: Asset[] = [];
|
|
109
|
+
await manager.rest.api.AssetResource.queryAssets({
|
|
110
|
+
realm: {
|
|
111
|
+
name: manager.displayRealm
|
|
112
|
+
},
|
|
113
|
+
select: {
|
|
114
|
+
attributes: [attributeName, 'location']
|
|
115
|
+
},
|
|
116
|
+
types: assetTypes,
|
|
117
|
+
|
|
118
|
+
}).then(response => {
|
|
119
|
+
assets = response.data;
|
|
120
|
+
this.markers = {};
|
|
121
|
+
}).catch((reason) => {
|
|
122
|
+
console.error(reason);
|
|
123
|
+
showSnackbar(undefined, "errorOccurred");
|
|
124
|
+
});
|
|
125
|
+
return assets;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
protected render(): TemplateResult {
|
|
130
|
+
return html`
|
|
131
|
+
<div style="height: 100%; display: flex; flex-direction: column; overflow: hidden;">
|
|
132
|
+
<or-map id="miniMap" class="or-map" .zoom="${this.widgetConfig.zoom}" .center="${this.widgetConfig.center}" .showGeoJson="${this.widgetConfig.showGeoJson}" style="flex: 1;">
|
|
133
|
+
${when(this.loadedAssets, () => {
|
|
134
|
+
return this.getMarkerTemplates();
|
|
135
|
+
})}
|
|
136
|
+
</or-map>
|
|
137
|
+
</div>
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
protected getMarkerTemplates(): TemplateResult[] {
|
|
142
|
+
return this.loadedAssets.filter((asset: Asset) => {
|
|
143
|
+
if (!asset.attributes) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const attr = asset.attributes[WellknownAttributes.LOCATION] as Attribute<GeoJSONPoint>;
|
|
147
|
+
return !attr || !attr.meta || !attr.meta.hasOwnProperty(WellknownMetaItems.SHOWONDASHBOARD) || !!Util.getMetaValue(WellknownMetaItems.SHOWONDASHBOARD, attr);
|
|
148
|
+
}).map(asset => {
|
|
149
|
+
if (this.markers) {
|
|
150
|
+
// Configure map marker asset settings
|
|
151
|
+
this.markers[asset.type!] = {attributeName: this.widgetConfig.attributeName!};
|
|
152
|
+
this.markers[asset.type!].showUnits = this.widgetConfig.showUnits;
|
|
153
|
+
this.markers[asset.type!].showLabel = this.widgetConfig.showLabels;
|
|
154
|
+
if (this.widgetConfig.valueType == 'boolean') {
|
|
155
|
+
(this.widgetConfig.boolColors as any).true = (this.widgetConfig.boolColors as any).true.replace("#", "");
|
|
156
|
+
(this.widgetConfig.boolColors as any).false = (this.widgetConfig.boolColors as any).false.replace("#", "");
|
|
157
|
+
this.markers[asset.type!].colours = this.widgetConfig.boolColors;
|
|
158
|
+
} else if (this.widgetConfig.valueType == 'text') {
|
|
159
|
+
var colors: AttributeMarkerColours = {type: 'string',};
|
|
160
|
+
(this.widgetConfig.textColors as [string, string][]).map((threshold) => {
|
|
161
|
+
colors[threshold[0] as string] = (threshold[1] as string).replace('#', '');
|
|
162
|
+
})
|
|
163
|
+
this.markers[asset.type!].colours = colors;
|
|
164
|
+
} else {
|
|
165
|
+
var ranges: AttributeMarkerColoursRange[] = [];
|
|
166
|
+
(this.widgetConfig.thresholds as [number, string][]).sort((x, y) => (x[0] > y[0]) ? -1 : 1).map((threshold, index) => {
|
|
167
|
+
var range: AttributeMarkerColoursRange = {
|
|
168
|
+
min: threshold[0],
|
|
169
|
+
colour: threshold[1].replace('#', '')
|
|
170
|
+
}
|
|
171
|
+
ranges.push(range);
|
|
172
|
+
})
|
|
173
|
+
var colorsNum: RangeAttributeMarkerColours = {
|
|
174
|
+
type: 'range',
|
|
175
|
+
ranges: ranges
|
|
176
|
+
};
|
|
177
|
+
this.markers[asset.type!].colours = colorsNum;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return html`
|
|
181
|
+
<or-map-marker-asset .asset="${asset}" .config="${this.markers}"></or-map-marker-asset>
|
|
182
|
+
`
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {css, html, PropertyValues, TemplateResult } from "lit";
|
|
2
|
+
import { customElement } from "lit/decorators.js";
|
|
3
|
+
import {OrAssetWidget} from "../util/or-asset-widget";
|
|
4
|
+
import {WidgetManifest} from "../util/or-widget";
|
|
5
|
+
import {WidgetConfig} from "../util/widget-config";
|
|
6
|
+
import {WidgetSettings} from "../util/widget-settings";
|
|
7
|
+
import {TableSettings} from "../settings/table-settings";
|
|
8
|
+
import {OrMwcTableRowClickEvent, TableColumn, TableRow, TableConfig} from "@openremote/or-mwc-components/or-mwc-table";
|
|
9
|
+
import {i18next} from "@openremote/or-translate";
|
|
10
|
+
import {Util} from "@openremote/core";
|
|
11
|
+
import {Asset, AssetModelUtil} from "@openremote/model";
|
|
12
|
+
import "@openremote/or-mwc-components/or-mwc-table";
|
|
13
|
+
|
|
14
|
+
export interface TableWidgetConfig extends WidgetConfig {
|
|
15
|
+
assetType?: string
|
|
16
|
+
assetIds: string[]
|
|
17
|
+
attributeNames: string[],
|
|
18
|
+
tableSize: number,
|
|
19
|
+
tableOptions: number[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getDefaultConfig(): TableWidgetConfig {
|
|
23
|
+
return {
|
|
24
|
+
assetType: undefined,
|
|
25
|
+
assetIds: [],
|
|
26
|
+
attributeNames: [],
|
|
27
|
+
tableSize: 10,
|
|
28
|
+
tableOptions: [10, 25, 100]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const styling = css`
|
|
33
|
+
#widget-wrapper {
|
|
34
|
+
height: 100%;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
}
|
|
37
|
+
`
|
|
38
|
+
|
|
39
|
+
@customElement("table-widget")
|
|
40
|
+
export class TableWidget extends OrAssetWidget {
|
|
41
|
+
|
|
42
|
+
protected widgetConfig!: TableWidgetConfig;
|
|
43
|
+
|
|
44
|
+
static getManifest(): WidgetManifest {
|
|
45
|
+
return {
|
|
46
|
+
displayName: "Table",
|
|
47
|
+
displayIcon: "table",
|
|
48
|
+
getContentHtml(config: WidgetConfig): OrAssetWidget {
|
|
49
|
+
return new TableWidget(config);
|
|
50
|
+
},
|
|
51
|
+
getDefaultConfig(): WidgetConfig {
|
|
52
|
+
return getDefaultConfig();
|
|
53
|
+
},
|
|
54
|
+
getSettingsHtml(config: WidgetConfig): WidgetSettings {
|
|
55
|
+
return new TableSettings(config);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static get styles() {
|
|
62
|
+
return [...super.styles, styling];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// TODO: Improve this to be more efficient
|
|
66
|
+
refreshContent(force: boolean): void {
|
|
67
|
+
this.widgetConfig = JSON.parse(JSON.stringify(this.widgetConfig)) as TableWidgetConfig;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Lit Lifecycle
|
|
71
|
+
protected willUpdate(changedProps: PropertyValues) {
|
|
72
|
+
if(changedProps.has('widgetConfig') && this.widgetConfig) {
|
|
73
|
+
this.loadAssets();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return super.willUpdate(changedProps);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
/* --------------------------------------- */
|
|
81
|
+
|
|
82
|
+
protected loadAssets() {
|
|
83
|
+
if(this.widgetConfig.assetIds.find(id => !this.isAssetLoaded(id))) {
|
|
84
|
+
this.queryAssets({
|
|
85
|
+
ids: this.widgetConfig.assetIds,
|
|
86
|
+
select: {
|
|
87
|
+
attributes: this.widgetConfig.attributeNames
|
|
88
|
+
}
|
|
89
|
+
}).then((assets) => {
|
|
90
|
+
this.loadedAssets = assets;
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected getColumns(attributeNames: string[]): TableColumn[] {
|
|
96
|
+
const referenceAsset: Asset | undefined = this.loadedAssets[0];
|
|
97
|
+
const attrColumns = attributeNames.map(attrName => {
|
|
98
|
+
let text = attrName;
|
|
99
|
+
let numeric = false;
|
|
100
|
+
if(this.widgetConfig.assetType && referenceAsset && referenceAsset.attributes && referenceAsset.attributes[attrName]) {
|
|
101
|
+
const attributeDescriptor = AssetModelUtil.getAttributeDescriptor(attrName, this.widgetConfig.assetType);
|
|
102
|
+
text = Util.getAttributeLabel(referenceAsset.attributes[attrName], attributeDescriptor, this.widgetConfig.assetType, true);
|
|
103
|
+
numeric = attributeDescriptor?.format?.asNumber || attributeDescriptor?.format?.asSlider || false;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
title: text,
|
|
107
|
+
isSortable: true,
|
|
108
|
+
isNumeric: numeric
|
|
109
|
+
} as TableColumn;
|
|
110
|
+
});
|
|
111
|
+
return Array.of({ title: i18next.t('assetName'), isSortable: true }, ...attrColumns);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
protected getRows(attributeNames: string[]): TableRow[] {
|
|
115
|
+
return this.loadedAssets.map(asset => {
|
|
116
|
+
const attrEntries = attributeNames.map(attrName => {
|
|
117
|
+
if(asset.attributes && asset.attributes[attrName]) {
|
|
118
|
+
const attributeDescriptor = AssetModelUtil.getAttributeDescriptor(attrName, asset.type!);
|
|
119
|
+
const strValue = Util.getAttributeValueAsString(asset.attributes[attrName], attributeDescriptor, asset.type, false);
|
|
120
|
+
const numValue = parseFloat(strValue);
|
|
121
|
+
return isNaN(numValue) ? strValue : numValue;
|
|
122
|
+
} else {
|
|
123
|
+
return 'N.A.'
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
const content: any[] = Array.of(asset.name!, ...attrEntries);
|
|
127
|
+
return {
|
|
128
|
+
content: content
|
|
129
|
+
};
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected render(): TemplateResult {
|
|
134
|
+
const tableConfig: any = {
|
|
135
|
+
fullHeight: true,
|
|
136
|
+
pagination: {
|
|
137
|
+
enable: true,
|
|
138
|
+
options: this.widgetConfig.tableOptions,
|
|
139
|
+
}
|
|
140
|
+
} as TableConfig
|
|
141
|
+
const columns: TableColumn[] = this.getColumns(this.widgetConfig.attributeNames);
|
|
142
|
+
const rows: TableRow[] = this.getRows(this.widgetConfig.attributeNames);
|
|
143
|
+
return html`
|
|
144
|
+
<div id="widget-wrapper">
|
|
145
|
+
<or-mwc-table .columns="${columns}" .rows="${rows}" .config="${tableConfig}" .paginationSize="${this.widgetConfig.tableSize}"
|
|
146
|
+
@or-mwc-table-row-click="${(ev: OrMwcTableRowClickEvent) => this.onTableRowClick(ev)}"
|
|
147
|
+
></or-mwc-table>
|
|
148
|
+
</div>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected onTableRowClick(ev: OrMwcTableRowClickEvent) {
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
}
|
package/tsconfig.json
ADDED