@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,93 @@
|
|
|
1
|
+
import {css, html, LitElement, TemplateResult } from "lit";
|
|
2
|
+
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
import { until } from "lit/directives/until.js";
|
|
4
|
+
import { when } from "lit/directives/when.js";
|
|
5
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
6
|
+
import {style} from "../style";
|
|
7
|
+
|
|
8
|
+
const styling = css`
|
|
9
|
+
:host {
|
|
10
|
+
height: auto !important;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#panel-wrapper {
|
|
14
|
+
border-top: 1px solid #E0E0E0;
|
|
15
|
+
border-bottom: 1px solid #E0E0E0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#panel-header {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
padding: 12px;
|
|
23
|
+
gap: 8px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#panel-title {
|
|
27
|
+
line-height: 100%;
|
|
28
|
+
font-weight: 700;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.panel-content {
|
|
32
|
+
padding: 0 16px;
|
|
33
|
+
max-height: 0;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
transition: max-height 0.2s cubic-bezier(0.4, 0.0, 0.2, 1) 0s, visibility 0s 0.2s; /* expanded -> collapsed */
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.panel-content--expanded {
|
|
39
|
+
max-height: 100vh;
|
|
40
|
+
overflow: visible;
|
|
41
|
+
transition: max-height 0.25s cubic-bezier(0.4, 0.0, 0.2, 1) 0s; /* collapsed -> expanded */
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
@customElement("settings-panel")
|
|
46
|
+
export class SettingsPanel extends LitElement {
|
|
47
|
+
|
|
48
|
+
@property()
|
|
49
|
+
protected expanded: boolean = false;
|
|
50
|
+
|
|
51
|
+
@property()
|
|
52
|
+
protected displayName?: string;
|
|
53
|
+
|
|
54
|
+
static get styles() {
|
|
55
|
+
return [styling, style]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected render(): TemplateResult {
|
|
59
|
+
const contentClasses = {
|
|
60
|
+
"panel-content": true,
|
|
61
|
+
"panel-content--expanded": this.expanded
|
|
62
|
+
}
|
|
63
|
+
return html`
|
|
64
|
+
<div id="panel-wrapper">
|
|
65
|
+
${until(this.generateHeader(this.expanded, this.displayName), html``)}
|
|
66
|
+
<div class="${classMap(contentClasses)}">
|
|
67
|
+
<div style="padding-bottom: 16px;">
|
|
68
|
+
<slot></slot>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public toggle(state?: boolean) {
|
|
76
|
+
this.expanded = state || !this.expanded
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected async generateHeader(expanded: boolean, title?: string): Promise<TemplateResult> {
|
|
80
|
+
return html`
|
|
81
|
+
<div id="panel-header" @click="${() => this.toggle()}">
|
|
82
|
+
<div id="panel-chevron">
|
|
83
|
+
<or-icon icon="${expanded ? 'chevron-down' : 'chevron-right'}"></or-icon>
|
|
84
|
+
</div>
|
|
85
|
+
${when(title, () => html`
|
|
86
|
+
<div id="panel-title">
|
|
87
|
+
<span><or-translate value="${this.displayName}"></or-translate></span>
|
|
88
|
+
</div>
|
|
89
|
+
`)}
|
|
90
|
+
</div>
|
|
91
|
+
`
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {LitElement, PropertyValues, TemplateResult } from "lit";
|
|
2
|
+
import { property } from "lit/decorators.js";
|
|
3
|
+
import {WidgetConfig} from "./widget-config";
|
|
4
|
+
import {style} from "../style";
|
|
5
|
+
|
|
6
|
+
export class WidgetSettingsChangedEvent extends CustomEvent<WidgetConfig> {
|
|
7
|
+
|
|
8
|
+
public static readonly NAME = "settings-changed";
|
|
9
|
+
|
|
10
|
+
constructor(widgetConfig: WidgetConfig) {
|
|
11
|
+
super(WidgetSettingsChangedEvent.NAME, {
|
|
12
|
+
bubbles: true,
|
|
13
|
+
composed: true,
|
|
14
|
+
detail: widgetConfig
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export abstract class WidgetSettings extends LitElement {
|
|
21
|
+
|
|
22
|
+
@property()
|
|
23
|
+
protected readonly widgetConfig: WidgetConfig;
|
|
24
|
+
|
|
25
|
+
static get styles() {
|
|
26
|
+
return [style];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected abstract render(): TemplateResult
|
|
30
|
+
|
|
31
|
+
constructor(config: WidgetConfig) {
|
|
32
|
+
super();
|
|
33
|
+
this.widgetConfig = config;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// Lit lifecycle for "on every update" which triggers on every property/state change
|
|
38
|
+
protected willUpdate(changedProps: PropertyValues) {
|
|
39
|
+
if(changedProps.has('widgetConfig') && this.widgetConfig) {
|
|
40
|
+
this.dispatchEvent(new WidgetSettingsChangedEvent(this.widgetConfig));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected notifyConfigUpdate() {
|
|
45
|
+
this.requestUpdate('widgetConfig');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/* ----------------------------- */
|
|
50
|
+
|
|
51
|
+
public getDisplayName?: () => string | undefined;
|
|
52
|
+
|
|
53
|
+
public setDisplayName?: (name?: string) => void;
|
|
54
|
+
|
|
55
|
+
public getEditMode?: () => boolean;
|
|
56
|
+
|
|
57
|
+
public getWidgetLocation?: () => { x?: number, y?: number, h?: number, w?: number }
|
|
58
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {css, html, PropertyValues, TemplateResult } from "lit";
|
|
2
|
+
import {OrAssetWidget} from "../util/or-asset-widget";
|
|
3
|
+
import {WidgetConfig} from "../util/widget-config";
|
|
4
|
+
import {Attribute, AttributeRef, WellknownMetaItems} from "@openremote/model";
|
|
5
|
+
import {WidgetManifest} from "../util/or-widget";
|
|
6
|
+
import {WidgetSettings} from "../util/widget-settings";
|
|
7
|
+
import { customElement, query, queryAll } from "lit/decorators.js";
|
|
8
|
+
import {AttributeInputSettings} from "../settings/attribute-input-settings";
|
|
9
|
+
import { when } from "lit/directives/when.js";
|
|
10
|
+
import {throttle} from "lodash";
|
|
11
|
+
import {Util} from "@openremote/core";
|
|
12
|
+
import "@openremote/or-attribute-input";
|
|
13
|
+
|
|
14
|
+
export interface AttributeInputWidgetConfig extends WidgetConfig {
|
|
15
|
+
attributeRefs: AttributeRef[];
|
|
16
|
+
readonly: boolean,
|
|
17
|
+
showHelperText: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getDefaultWidgetConfig() {
|
|
21
|
+
return {
|
|
22
|
+
attributeRefs: [],
|
|
23
|
+
readonly: false,
|
|
24
|
+
showHelperText: true
|
|
25
|
+
} as AttributeInputWidgetConfig
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const styling = css`
|
|
29
|
+
#widget-wrapper {
|
|
30
|
+
height: 100%;
|
|
31
|
+
display: flex;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
align-items: center;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.attr-input {
|
|
38
|
+
width: 100%;
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
@customElement("attribute-input-widget")
|
|
44
|
+
export class AttributeInputWidget extends OrAssetWidget {
|
|
45
|
+
|
|
46
|
+
protected widgetConfig!: AttributeInputWidgetConfig;
|
|
47
|
+
|
|
48
|
+
@query("#widget-wrapper")
|
|
49
|
+
protected widgetWrapperElem?: HTMLElement;
|
|
50
|
+
|
|
51
|
+
@queryAll(".attr-input")
|
|
52
|
+
protected attributeInputElems?: NodeList;
|
|
53
|
+
|
|
54
|
+
protected resizeObserver?: ResizeObserver;
|
|
55
|
+
|
|
56
|
+
static getManifest(): WidgetManifest {
|
|
57
|
+
return {
|
|
58
|
+
displayName: "Attribute",
|
|
59
|
+
displayIcon: "form-textbox",
|
|
60
|
+
getContentHtml(config: WidgetConfig): OrAssetWidget {
|
|
61
|
+
return new AttributeInputWidget(config);
|
|
62
|
+
},
|
|
63
|
+
getDefaultConfig(): WidgetConfig {
|
|
64
|
+
return getDefaultWidgetConfig();
|
|
65
|
+
},
|
|
66
|
+
getSettingsHtml(config: WidgetConfig): WidgetSettings {
|
|
67
|
+
return new AttributeInputSettings(config);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// TODO: Improve this to be more efficient
|
|
74
|
+
refreshContent(force: boolean): void {
|
|
75
|
+
this.widgetConfig = JSON.parse(JSON.stringify(this.widgetConfig)) as AttributeInputWidgetConfig;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static get styles() {
|
|
79
|
+
return [...super.styles, styling];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
disconnectedCallback() {
|
|
83
|
+
super.disconnectedCallback();
|
|
84
|
+
this.resizeObserver?.disconnect();
|
|
85
|
+
delete this.resizeObserver;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected updated(changedProps: PropertyValues) {
|
|
89
|
+
|
|
90
|
+
// If widgetConfig, and the attributeRefs of them have changed...
|
|
91
|
+
if(changedProps.has("widgetConfig") && this.widgetConfig) {
|
|
92
|
+
const attributeRefs = this.widgetConfig.attributeRefs;
|
|
93
|
+
if(attributeRefs.length > 0 && !this.isAttributeRefLoaded(attributeRefs[0])) {
|
|
94
|
+
this.loadAssets(attributeRefs);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Workaround for an issue with scalability of or-attribute-input when using 'display: flex'.
|
|
99
|
+
// The percentage slider doesn't scale properly, causing the dragging knob to glitch.
|
|
100
|
+
// Why? Because the Material Design element listens to a window resize, not a container resize.
|
|
101
|
+
// So we manually trigger this event when the attribute-input-widget changes in size.
|
|
102
|
+
if(!this.resizeObserver && this.widgetWrapperElem) {
|
|
103
|
+
this.resizeObserver = new ResizeObserver(throttle(() => {
|
|
104
|
+
window.dispatchEvent(new Event('resize'));
|
|
105
|
+
}, 200));
|
|
106
|
+
this.resizeObserver.observe(this.widgetWrapperElem);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return super.updated(changedProps);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected loadAssets(attributeRefs: AttributeRef[]) {
|
|
113
|
+
this.fetchAssets(attributeRefs).then((assets) => {
|
|
114
|
+
this.loadedAssets = assets;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
protected render(): TemplateResult {
|
|
119
|
+
const config = this.widgetConfig;
|
|
120
|
+
const attribute = (config.attributeRefs.length > 0 && this.loadedAssets[0]?.attributes) ? this.loadedAssets[0].attributes[config.attributeRefs[0].name!] : undefined;
|
|
121
|
+
const readOnlyMetaItem = Util.getMetaValue(WellknownMetaItems.READONLY, attribute);
|
|
122
|
+
return html`
|
|
123
|
+
${when(config.attributeRefs.length > 0 && attribute && this.loadedAssets && this.loadedAssets.length > 0, () => {
|
|
124
|
+
return html`
|
|
125
|
+
<div id="widget-wrapper">
|
|
126
|
+
<or-attribute-input class="attr-input" fullWidth
|
|
127
|
+
.assetType="${this.loadedAssets[0]?.type}"
|
|
128
|
+
.attribute="${attribute}"
|
|
129
|
+
.assetId="${this.loadedAssets[0]?.id}"
|
|
130
|
+
.disabled="${!this.loadedAssets}"
|
|
131
|
+
.readonly="${config.readonly || readOnlyMetaItem || this.getEditMode!()}"
|
|
132
|
+
.hasHelperText="${config.showHelperText}"
|
|
133
|
+
></or-attribute-input>
|
|
134
|
+
</div>
|
|
135
|
+
`
|
|
136
|
+
}, () => html`
|
|
137
|
+
<div style="height: 100%; display: flex; justify-content: center; align-items: center;">
|
|
138
|
+
<span><or-translate value="noAttributesConnected"></or-translate></span>
|
|
139
|
+
</div>
|
|
140
|
+
`)}
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import {AssetDatapointLTTBQuery, AssetDatapointQueryUnion, Attribute, AttributeRef} from "@openremote/model";
|
|
2
|
+
import {html, PropertyValues, TemplateResult } from "lit";
|
|
3
|
+
import { when } from "lit/directives/when.js";
|
|
4
|
+
import {TimePresetCallback} from "@openremote/or-chart";
|
|
5
|
+
import moment from "moment";
|
|
6
|
+
import {OrAssetWidget} from "../util/or-asset-widget";
|
|
7
|
+
import { customElement, state } from "lit/decorators.js";
|
|
8
|
+
import {WidgetConfig} from "../util/widget-config";
|
|
9
|
+
import {OrWidget, WidgetManifest} from "../util/or-widget";
|
|
10
|
+
import {ChartSettings} from "../settings/chart-settings";
|
|
11
|
+
import {WidgetSettings} from "../util/widget-settings";
|
|
12
|
+
import "@openremote/or-chart";
|
|
13
|
+
|
|
14
|
+
export interface ChartWidgetConfig extends WidgetConfig {
|
|
15
|
+
attributeRefs: AttributeRef[];
|
|
16
|
+
rightAxisAttributes: AttributeRef[];
|
|
17
|
+
datapointQuery: AssetDatapointQueryUnion;
|
|
18
|
+
chartOptions?: any; // ChartConfiguration<"line", ScatterDataPoint[]>
|
|
19
|
+
showTimestampControls: boolean;
|
|
20
|
+
defaultTimePresetKey: string;
|
|
21
|
+
showLegend: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getDefaultTimePresetOptions(): Map<string, TimePresetCallback> {
|
|
25
|
+
return new Map<string, TimePresetCallback>([
|
|
26
|
+
["lastHour", (date: Date) => [moment(date).subtract(1, 'hour').toDate(), date]],
|
|
27
|
+
["last24Hours", (date: Date) => [moment(date).subtract(24, 'hours').toDate(), date]],
|
|
28
|
+
["last7Days", (date: Date) => [moment(date).subtract(7, 'days').toDate(), date]],
|
|
29
|
+
["last30Days", (date: Date) => [moment(date).subtract(30, 'days').toDate(), date]],
|
|
30
|
+
["last90Days", (date: Date) => [moment(date).subtract(90, 'days').toDate(), date]],
|
|
31
|
+
["last6Months", (date: Date) => [moment(date).subtract(6, 'months').toDate(), date]],
|
|
32
|
+
["lastYear", (date: Date) => [moment(date).subtract(1, 'year').toDate(), date]],
|
|
33
|
+
["thisHour", (date: Date) => [moment(date).startOf('hour').toDate(), moment(date).endOf('hour').toDate()]],
|
|
34
|
+
["thisDay", (date: Date) => [moment(date).startOf('day').toDate(), moment(date).endOf('day').toDate()]],
|
|
35
|
+
["thisWeek", (date: Date) => [moment(date).startOf('isoWeek').toDate(), moment(date).endOf('isoWeek').toDate()]],
|
|
36
|
+
["thisMonth", (date: Date) => [moment(date).startOf('month').toDate(), moment(date).endOf('month').toDate()]],
|
|
37
|
+
["thisYear", (date: Date) => [moment(date).startOf('year').toDate(), moment(date).endOf('year').toDate()]],
|
|
38
|
+
["yesterday", (date: Date) => [moment(date).subtract(24, 'hours').startOf('day').toDate(), moment(date).subtract(24, 'hours').endOf('day').toDate()]],
|
|
39
|
+
["thisDayLastWeek", (date: Date) => [moment(date).subtract(1, 'week').startOf('day').toDate(), moment(date).subtract(1, 'week').endOf('day').toDate()]],
|
|
40
|
+
["previousWeek", (date: Date) => [moment(date).subtract(1, 'week').startOf('isoWeek').toDate(), moment(date).subtract(1, 'week').endOf('isoWeek').toDate()]],
|
|
41
|
+
["previousMonth", (date: Date) => [moment(date).subtract(1, 'month').startOf('month').toDate(), moment(date).subtract(1, 'month').endOf('month').toDate()]],
|
|
42
|
+
["previousYear", (date: Date) => [moment(date).subtract(1, 'year').startOf('year').toDate(), moment(date).subtract(1, 'year').endOf('year').toDate()]]
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getDefaultSamplingOptions(): Map<string, string> {
|
|
47
|
+
return new Map<string, string>([["lttb", 'lttb'], ["withInterval", 'interval']]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getDefaultWidgetConfig(): ChartWidgetConfig {
|
|
51
|
+
const preset = "last24Hours"
|
|
52
|
+
const dateFunc = getDefaultTimePresetOptions().get(preset) as TimePresetCallback;
|
|
53
|
+
const dates = dateFunc(new Date());
|
|
54
|
+
return {
|
|
55
|
+
attributeRefs: [],
|
|
56
|
+
rightAxisAttributes: [],
|
|
57
|
+
datapointQuery: {
|
|
58
|
+
type: "lttb",
|
|
59
|
+
fromTimestamp: dates[0].getTime(),
|
|
60
|
+
toTimestamp: dates[1].getTime()
|
|
61
|
+
},
|
|
62
|
+
chartOptions: {
|
|
63
|
+
options: {
|
|
64
|
+
scales: {
|
|
65
|
+
y: {
|
|
66
|
+
min: undefined,
|
|
67
|
+
max: undefined
|
|
68
|
+
},
|
|
69
|
+
y1: {
|
|
70
|
+
min: undefined,
|
|
71
|
+
max: undefined
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
showTimestampControls: false,
|
|
77
|
+
defaultTimePresetKey: preset,
|
|
78
|
+
showLegend: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* --------------------------------------------------- */
|
|
83
|
+
|
|
84
|
+
@customElement('chart-widget')
|
|
85
|
+
export class ChartWidget extends OrAssetWidget {
|
|
86
|
+
|
|
87
|
+
@state()
|
|
88
|
+
protected datapointQuery!: AssetDatapointQueryUnion;
|
|
89
|
+
|
|
90
|
+
// Override of widgetConfig with extended type
|
|
91
|
+
protected widgetConfig!: ChartWidgetConfig;
|
|
92
|
+
|
|
93
|
+
static getManifest(): WidgetManifest {
|
|
94
|
+
return {
|
|
95
|
+
displayName: "Line Chart",
|
|
96
|
+
displayIcon: "chart-line",
|
|
97
|
+
minColumnWidth: 2,
|
|
98
|
+
minColumnHeight: 2,
|
|
99
|
+
getContentHtml(config: ChartWidgetConfig): OrWidget {
|
|
100
|
+
return new ChartWidget(config);
|
|
101
|
+
},
|
|
102
|
+
getSettingsHtml(config: ChartWidgetConfig): WidgetSettings {
|
|
103
|
+
const settings = new ChartSettings(config);
|
|
104
|
+
settings.setTimePresetOptions(getDefaultTimePresetOptions());
|
|
105
|
+
settings.setSamplingOptions(getDefaultSamplingOptions());
|
|
106
|
+
return settings;
|
|
107
|
+
},
|
|
108
|
+
getDefaultConfig(): ChartWidgetConfig {
|
|
109
|
+
return getDefaultWidgetConfig();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Method called on every refresh/reload of the widget
|
|
115
|
+
// We either refresh the datapointQuery or the full widgetConfig depending on the force parameter.
|
|
116
|
+
// TODO: Improve this to a more efficient approach, instead of duplicating the object
|
|
117
|
+
public refreshContent(force: boolean) {
|
|
118
|
+
if(!force) {
|
|
119
|
+
const datapointQuery = JSON.parse(JSON.stringify(this.widgetConfig.datapointQuery)) as AssetDatapointQueryUnion;
|
|
120
|
+
datapointQuery.fromTimestamp = undefined;
|
|
121
|
+
datapointQuery.toTimestamp = undefined;
|
|
122
|
+
this.datapointQuery = datapointQuery;
|
|
123
|
+
} else {
|
|
124
|
+
this.widgetConfig = JSON.parse(JSON.stringify(this.widgetConfig)) as ChartWidgetConfig;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
/* ---------------------------------- */
|
|
130
|
+
|
|
131
|
+
// WebComponent lifecycle method, that occurs DURING every state update
|
|
132
|
+
protected willUpdate(changedProps: PropertyValues) {
|
|
133
|
+
|
|
134
|
+
// Add datapointQuery if not set yet (due to migration)
|
|
135
|
+
if(!this.widgetConfig.datapointQuery) {
|
|
136
|
+
this.widgetConfig.datapointQuery = this.getDefaultQuery();
|
|
137
|
+
if(!changedProps.has("widgetConfig")) {
|
|
138
|
+
changedProps.set("widgetConfig", this.widgetConfig);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if(changedProps.has('widgetConfig') && this.widgetConfig) {
|
|
143
|
+
this.datapointQuery = this.widgetConfig.datapointQuery;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return super.willUpdate(changedProps);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// WebComponent lifecycle method, that occurs AFTER every state update
|
|
150
|
+
protected updated(changedProps: Map<string, any>) {
|
|
151
|
+
super.updated(changedProps);
|
|
152
|
+
|
|
153
|
+
// If widgetConfig, and the attributeRefs of them have changed...
|
|
154
|
+
if(changedProps.has("widgetConfig") && this.widgetConfig) {
|
|
155
|
+
const attributeRefs = this.widgetConfig.attributeRefs;
|
|
156
|
+
const missingAssets = attributeRefs?.filter((attrRef: AttributeRef) => !this.isAttributeRefLoaded(attrRef));
|
|
157
|
+
if (missingAssets.length > 0) {
|
|
158
|
+
|
|
159
|
+
// Fetch the new list of assets
|
|
160
|
+
this.fetchAssets(attributeRefs).then((assets) => {
|
|
161
|
+
this.loadedAssets = assets;
|
|
162
|
+
this.assetAttributes = attributeRefs?.map((attrRef: AttributeRef) => {
|
|
163
|
+
const assetIndex = assets.findIndex((asset) => asset.id === attrRef.id);
|
|
164
|
+
const foundAsset = assetIndex >= 0 ? assets[assetIndex] : undefined;
|
|
165
|
+
return foundAsset && foundAsset.attributes ? [assetIndex, foundAsset.attributes[attrRef.name!]] : undefined;
|
|
166
|
+
}).filter((indexAndAttr: any) => !!indexAndAttr) as [number, Attribute<any>][];
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return super.updated(changedProps);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
protected render(): TemplateResult {
|
|
175
|
+
return html`
|
|
176
|
+
${when(this.loadedAssets && this.assetAttributes && this.loadedAssets.length > 0 && this.assetAttributes.length > 0, () => {
|
|
177
|
+
return html`
|
|
178
|
+
<or-chart .assets="${this.loadedAssets}" .assetAttributes="${this.assetAttributes}" .rightAxisAttributes="${this.widgetConfig.rightAxisAttributes}"
|
|
179
|
+
.showLegend="${(this.widgetConfig?.showLegend != null) ? this.widgetConfig?.showLegend : true}"
|
|
180
|
+
.attributeControls="${false}" .timestampControls="${!this.widgetConfig?.showTimestampControls}"
|
|
181
|
+
.timePresetOptions="${getDefaultTimePresetOptions()}" .timePresetKey="${this.widgetConfig?.defaultTimePresetKey}"
|
|
182
|
+
.datapointQuery="${this.datapointQuery}" .chartOptions="${this.widgetConfig?.chartOptions}"
|
|
183
|
+
style="height: 100%"
|
|
184
|
+
></or-chart>
|
|
185
|
+
`;
|
|
186
|
+
}, () => {
|
|
187
|
+
return html`
|
|
188
|
+
<div style="height: 100%; display: flex; justify-content: center; align-items: center;">
|
|
189
|
+
<span><or-translate value="noAttributesConnected"></or-translate></span>
|
|
190
|
+
</div>
|
|
191
|
+
`
|
|
192
|
+
})}
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
protected getDefaultQuery(): AssetDatapointLTTBQuery {
|
|
197
|
+
return {
|
|
198
|
+
type: "lttb",
|
|
199
|
+
fromTimestamp: moment().set('minute', -60).toDate().getTime(),
|
|
200
|
+
toTimestamp: moment().set('minute', 60).toDate().getTime()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {html, TemplateResult} from "lit";
|
|
2
|
+
import {customElement, state} from "lit/decorators.js";
|
|
3
|
+
import {OrAssetWidget} from "../util/or-asset-widget";
|
|
4
|
+
import {WidgetConfig} from "../util/widget-config";
|
|
5
|
+
import {Asset, Attribute, AttributeRef} from "@openremote/model";
|
|
6
|
+
import {OrWidget, WidgetManifest} from "../util/or-widget";
|
|
7
|
+
import {WidgetSettings} from "../util/widget-settings";
|
|
8
|
+
import {GaugeSettings} from "../settings/gauge-settings";
|
|
9
|
+
import {when} from "lit/directives/when.js";
|
|
10
|
+
import "@openremote/or-gauge";
|
|
11
|
+
|
|
12
|
+
export interface GaugeWidgetConfig extends WidgetConfig {
|
|
13
|
+
attributeRefs: AttributeRef[];
|
|
14
|
+
thresholds: [number, string][];
|
|
15
|
+
decimals: number;
|
|
16
|
+
min: number;
|
|
17
|
+
max: number;
|
|
18
|
+
valueType: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getDefaultWidgetConfig(): GaugeWidgetConfig {
|
|
22
|
+
return {
|
|
23
|
+
attributeRefs: [],
|
|
24
|
+
thresholds: [[0, "#4caf50"], [75, "#ff9800"], [90, "#ef5350"]], // colors from https://mui.com/material-ui/customization/palette/ as reference (since material has no official colors)
|
|
25
|
+
decimals: 0,
|
|
26
|
+
min: 0,
|
|
27
|
+
max: 100,
|
|
28
|
+
valueType: 'number',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@customElement("gauge-widget")
|
|
33
|
+
export class GaugeWidget extends OrAssetWidget {
|
|
34
|
+
|
|
35
|
+
// Override of widgetConfig with extended type
|
|
36
|
+
protected widgetConfig!: GaugeWidgetConfig;
|
|
37
|
+
|
|
38
|
+
static getManifest(): WidgetManifest {
|
|
39
|
+
return {
|
|
40
|
+
displayName: "Gauge",
|
|
41
|
+
displayIcon: "gauge",
|
|
42
|
+
minColumnWidth: 1,
|
|
43
|
+
minColumnHeight: 1,
|
|
44
|
+
getContentHtml(config: GaugeWidgetConfig): OrWidget {
|
|
45
|
+
return new GaugeWidget(config);
|
|
46
|
+
},
|
|
47
|
+
getSettingsHtml(config: GaugeWidgetConfig): WidgetSettings {
|
|
48
|
+
return new GaugeSettings(config);
|
|
49
|
+
},
|
|
50
|
+
getDefaultConfig(): GaugeWidgetConfig {
|
|
51
|
+
return getDefaultWidgetConfig();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public refreshContent(force: boolean) {
|
|
57
|
+
this.loadAssets(this.widgetConfig.attributeRefs);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// WebComponent lifecycle method, that occurs AFTER every state update
|
|
61
|
+
protected updated(changedProps: Map<string, any>) {
|
|
62
|
+
super.updated(changedProps);
|
|
63
|
+
|
|
64
|
+
// If widgetConfig, and the attributeRefs of them have changed...
|
|
65
|
+
if(changedProps.has("widgetConfig") && this.widgetConfig) {
|
|
66
|
+
const attributeRefs = this.widgetConfig.attributeRefs;
|
|
67
|
+
|
|
68
|
+
// Check if list of attributes has changed, based on the cached assets
|
|
69
|
+
const loadedRefs: AttributeRef[] = attributeRefs?.filter((attrRef: AttributeRef) => this.isAttributeRefLoaded(attrRef));
|
|
70
|
+
if (loadedRefs?.length !== (attributeRefs ? attributeRefs.length : 0)) {
|
|
71
|
+
|
|
72
|
+
// Fetch the new list of assets
|
|
73
|
+
this.loadAssets(attributeRefs);
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return super.updated(changedProps);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
protected loadAssets(attributeRefs: AttributeRef[]) {
|
|
81
|
+
this.fetchAssets(attributeRefs).then((assets) => {
|
|
82
|
+
this.loadedAssets = assets;
|
|
83
|
+
this.assetAttributes = attributeRefs?.map((attrRef: AttributeRef) => {
|
|
84
|
+
const assetIndex = assets.findIndex((asset) => asset.id === attrRef.id);
|
|
85
|
+
const foundAsset = assetIndex >= 0 ? assets[assetIndex] : undefined;
|
|
86
|
+
return foundAsset && foundAsset.attributes ? [assetIndex, foundAsset.attributes[attrRef.name!]] : undefined;
|
|
87
|
+
}).filter((indexAndAttr: any) => !!indexAndAttr) as [number, Attribute<any>][];
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
protected render(): TemplateResult {
|
|
93
|
+
return html`
|
|
94
|
+
${when(this.loadedAssets && this.assetAttributes && this.loadedAssets.length > 0 && this.assetAttributes.length > 0, () => {
|
|
95
|
+
return html`
|
|
96
|
+
<or-gauge .asset="${this.loadedAssets[0]}" .assetAttribute="${this.assetAttributes[0]}" .thresholds="${this.widgetConfig.thresholds}"
|
|
97
|
+
.decimals="${this.widgetConfig.decimals}" .min="${this.widgetConfig.min}" .max="${this.widgetConfig.max}"
|
|
98
|
+
style="height: 100%; overflow: hidden;">
|
|
99
|
+
</or-gauge>
|
|
100
|
+
`;
|
|
101
|
+
}, () => {
|
|
102
|
+
return html`
|
|
103
|
+
<div style="height: 100%; display: flex; justify-content: center; align-items: center;">
|
|
104
|
+
<span><or-translate value="noAttributeConnected"></or-translate></span>
|
|
105
|
+
</div>
|
|
106
|
+
`
|
|
107
|
+
})}
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
}
|