@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,304 @@
|
|
|
1
|
+
import {css, html, LitElement, PropertyValues} from 'lit';
|
|
2
|
+
import {customElement, property} from 'lit/decorators.js';
|
|
3
|
+
import {InputType} from '@openremote/or-mwc-components/or-mwc-input';
|
|
4
|
+
import '@openremote/or-icon';
|
|
5
|
+
import {style} from './style';
|
|
6
|
+
import {Dashboard} from '@openremote/model';
|
|
7
|
+
import manager from '@openremote/core';
|
|
8
|
+
import {ListItem} from '@openremote/or-mwc-components/or-mwc-list';
|
|
9
|
+
import '@openremote/or-mwc-components/or-mwc-menu';
|
|
10
|
+
import {showOkCancelDialog} from '@openremote/or-mwc-components/or-mwc-dialog';
|
|
11
|
+
import {i18next} from '@openremote/or-translate';
|
|
12
|
+
import {showSnackbar} from '@openremote/or-mwc-components/or-mwc-snackbar';
|
|
13
|
+
import {style as OrAssetTreeStyle} from '@openremote/or-asset-tree';
|
|
14
|
+
import {DashboardService, DashboardSizeOption} from './service/dashboard-service';
|
|
15
|
+
import {isAxiosError} from '@openremote/rest';
|
|
16
|
+
|
|
17
|
+
// language=css
|
|
18
|
+
const treeStyling = css`
|
|
19
|
+
#header-btns {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: row;
|
|
22
|
+
padding-right: 5px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.node-container {
|
|
26
|
+
align-items: center;
|
|
27
|
+
padding-left: 10px;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
@customElement('or-dashboard-tree')
|
|
32
|
+
export class OrDashboardTree extends LitElement {
|
|
33
|
+
|
|
34
|
+
static get styles() {
|
|
35
|
+
return [style, treeStyling, OrAssetTreeStyle];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@property()
|
|
39
|
+
protected dashboards?: Dashboard[];
|
|
40
|
+
|
|
41
|
+
@property()
|
|
42
|
+
protected selected?: Dashboard;
|
|
43
|
+
|
|
44
|
+
@property()
|
|
45
|
+
protected readonly realm: string = manager.displayRealm;
|
|
46
|
+
|
|
47
|
+
@property() // REQUIRED
|
|
48
|
+
protected readonly userId!: string;
|
|
49
|
+
|
|
50
|
+
@property()
|
|
51
|
+
protected readonly readonly = true;
|
|
52
|
+
|
|
53
|
+
@property() // Whether the selected dashboard has been changed or not.
|
|
54
|
+
protected readonly hasChanged = false;
|
|
55
|
+
|
|
56
|
+
@property()
|
|
57
|
+
protected showControls = true;
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
/* --------------- */
|
|
61
|
+
|
|
62
|
+
shouldUpdate(changedProperties: PropertyValues) {
|
|
63
|
+
if (changedProperties.size === 1) {
|
|
64
|
+
|
|
65
|
+
// Prevent any update since it is not necessary in its current state.
|
|
66
|
+
// However, do update when dashboard is saved (aka when hasChanged is set back to false)
|
|
67
|
+
if (changedProperties.has('hasChanged') && this.hasChanged) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return super.shouldUpdate(changedProperties);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
updated(changedProperties: PropertyValues) {
|
|
75
|
+
if (!this.dashboards) {
|
|
76
|
+
this.getAllDashboards();
|
|
77
|
+
}
|
|
78
|
+
if (changedProperties.has('dashboards') && changedProperties.get('dashboards') != null) {
|
|
79
|
+
this.dispatchEvent(new CustomEvent('updated', {detail: this.dashboards}));
|
|
80
|
+
}
|
|
81
|
+
if (changedProperties.has('selected')) {
|
|
82
|
+
this.dispatchEvent(new CustomEvent('select', {detail: this.selected}));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/* ---------------------- */
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
// Gets ALL dashboards the user has access to.
|
|
91
|
+
// TODO: Optimize this by querying the database, or limit the JSON that is fetched.
|
|
92
|
+
private async getAllDashboards() {
|
|
93
|
+
return manager.rest.api.DashboardResource.getAllRealmDashboards(this.realm)
|
|
94
|
+
.then(result => {
|
|
95
|
+
this.dashboards = result.data;
|
|
96
|
+
}).catch(reason => {
|
|
97
|
+
console.error(reason);
|
|
98
|
+
showSnackbar(undefined, 'errorOccurred');
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Selects the dashboard (id).
|
|
103
|
+
// Will emit a "select" event during the lifecycle
|
|
104
|
+
private selectDashboard(id: string | Dashboard | undefined) {
|
|
105
|
+
if (typeof id === 'string') {
|
|
106
|
+
this.selected = this.dashboards?.find(dashboard => dashboard.id === id);
|
|
107
|
+
} else {
|
|
108
|
+
this.selected = id;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Creates a dashboard, and adds it onto the list.
|
|
113
|
+
// Dispatch a "created" event, to let parent elements know a new dashboard has been created.
|
|
114
|
+
// It will automatically select the newly created dashboard.
|
|
115
|
+
private createDashboard(size: DashboardSizeOption) {
|
|
116
|
+
DashboardService.create(undefined, size, this.realm).then(d => {
|
|
117
|
+
if (!this.dashboards) {
|
|
118
|
+
this.dashboards = [d] as Dashboard[];
|
|
119
|
+
} else {
|
|
120
|
+
this.dashboards.push(d);
|
|
121
|
+
this.requestUpdate('dashboards');
|
|
122
|
+
}
|
|
123
|
+
this.dispatchEvent(new CustomEvent('created', {detail: {d}}));
|
|
124
|
+
this.selectDashboard(d);
|
|
125
|
+
|
|
126
|
+
}).catch(e => {
|
|
127
|
+
console.error(e);
|
|
128
|
+
if (isAxiosError(e) && e.response?.status === 404) {
|
|
129
|
+
showSnackbar(undefined, 'noDashboardFound');
|
|
130
|
+
} else {
|
|
131
|
+
showSnackbar(undefined, 'errorOccurred');
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected duplicateDashboard(dashboard: Dashboard) {
|
|
137
|
+
if (dashboard?.id !== null) {
|
|
138
|
+
if(this.hasChanged) {
|
|
139
|
+
this.showDiscardChangesModal().then(ok => {
|
|
140
|
+
if(ok) {
|
|
141
|
+
this._doDuplicateDashboard(dashboard);
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
} else {
|
|
145
|
+
this._doDuplicateDashboard(dashboard);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
console.warn("Tried duplicating a NULL dashboard!");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected _doDuplicateDashboard(dashboard: Dashboard) {
|
|
153
|
+
const newDashboard = JSON.parse(JSON.stringify(dashboard)) as Dashboard;
|
|
154
|
+
newDashboard.displayName = (newDashboard.displayName + ' copy');
|
|
155
|
+
DashboardService.create(newDashboard, undefined, this.realm).then(d => {
|
|
156
|
+
if (!this.dashboards) {
|
|
157
|
+
this.dashboards = [d] as Dashboard[];
|
|
158
|
+
} else {
|
|
159
|
+
this.dashboards.push(d);
|
|
160
|
+
this.requestUpdate('dashboards');
|
|
161
|
+
}
|
|
162
|
+
this.dispatchEvent(new CustomEvent('created', {detail: {d}}));
|
|
163
|
+
this.selectDashboard(d.id);
|
|
164
|
+
}).catch(e => {
|
|
165
|
+
console.error(e);
|
|
166
|
+
if (isAxiosError(e) && e.response?.status === 404) {
|
|
167
|
+
showSnackbar(undefined, 'noDashboardFound');
|
|
168
|
+
} else {
|
|
169
|
+
showSnackbar(undefined, 'errorOccurred');
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private deleteDashboard(dashboard: Dashboard) {
|
|
175
|
+
if (dashboard.id != null) {
|
|
176
|
+
DashboardService.delete(dashboard.id, this.realm).then(() => {
|
|
177
|
+
this.getAllDashboards();
|
|
178
|
+
}).catch(reason => {
|
|
179
|
+
console.error(reason);
|
|
180
|
+
showSnackbar(undefined, 'errorOccurred');
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ---------------------- */
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
// When a user clicks on a dashboard within the list...
|
|
189
|
+
protected onDashboardClick(dashboardId: string) {
|
|
190
|
+
if (dashboardId !== this.selected?.id) {
|
|
191
|
+
if (this.hasChanged) {
|
|
192
|
+
this.showDiscardChangesModal().then(ok => {
|
|
193
|
+
if(ok) this.selectDashboard(dashboardId);
|
|
194
|
+
})
|
|
195
|
+
} else {
|
|
196
|
+
this.selectDashboard(dashboardId);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
protected showDiscardChangesModal(): Promise<boolean> {
|
|
202
|
+
return showOkCancelDialog(i18next.t('areYouSure'), i18next.t('confirmContinueDashboardModified'), i18next.t('discard'));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Element render method
|
|
206
|
+
// TODO: Move dashboard filtering to separate method.
|
|
207
|
+
protected render() {
|
|
208
|
+
const dashboardItems: ListItem[][] = [];
|
|
209
|
+
if (this.dashboards && this.dashboards.length > 0) {
|
|
210
|
+
if (this.userId) {
|
|
211
|
+
const myDashboards: Dashboard[] = [];
|
|
212
|
+
const otherDashboards: Dashboard[] = [];
|
|
213
|
+
this.dashboards?.forEach(d => {
|
|
214
|
+
(d.ownerId === this.userId) ? myDashboards.push(d) : otherDashboards.push(d);
|
|
215
|
+
});
|
|
216
|
+
if (myDashboards.length > 0) {
|
|
217
|
+
const items: ListItem[] = [];
|
|
218
|
+
myDashboards.sort((a, b) => a.displayName ? a.displayName.localeCompare(b.displayName) : 0).forEach(d => {
|
|
219
|
+
items.push({icon: 'view-dashboard', text: d.displayName, value: d.id});
|
|
220
|
+
});
|
|
221
|
+
dashboardItems.push(items);
|
|
222
|
+
}
|
|
223
|
+
if (otherDashboards.length > 0) {
|
|
224
|
+
const items: ListItem[] = [];
|
|
225
|
+
otherDashboards.sort((a, b) => a.displayName ? a.displayName.localeCompare(b.displayName) : 0).forEach(d => {
|
|
226
|
+
items.push({icon: 'view-dashboard', text: d.displayName, value: d.id});
|
|
227
|
+
});
|
|
228
|
+
dashboardItems.push(items);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return html`
|
|
233
|
+
<div id="menu-header">
|
|
234
|
+
<div id="title-container">
|
|
235
|
+
<span id="title"><or-translate value="insights"></or-translate></span>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<!-- Controls header -->
|
|
239
|
+
${this.showControls ? html`
|
|
240
|
+
<div id="header-btns">
|
|
241
|
+
${this.selected != null ? html`
|
|
242
|
+
<or-mwc-input type="${InputType.BUTTON}" icon="close" @or-mwc-input-changed="${() => {
|
|
243
|
+
this.selectDashboard(undefined);
|
|
244
|
+
}}"></or-mwc-input>
|
|
245
|
+
${!this.readonly ? html`
|
|
246
|
+
<or-mwc-input type="${InputType.BUTTON}" class="hideMobile" icon="content-copy"
|
|
247
|
+
@or-mwc-input-changed="${() => {
|
|
248
|
+
this.duplicateDashboard(this.selected!);
|
|
249
|
+
}}"
|
|
250
|
+
></or-mwc-input>
|
|
251
|
+
<or-mwc-input type="${InputType.BUTTON}" icon="delete" @or-mwc-input-changed="${() => {
|
|
252
|
+
if (this.selected != null) {
|
|
253
|
+
showOkCancelDialog(i18next.t('areYouSure'), i18next.t('dashboard.deletePermanentWarning', {dashboard: this.selected.displayName}), i18next.t('delete')).then((ok: boolean) => {
|
|
254
|
+
if (ok) {
|
|
255
|
+
this.deleteDashboard(this.selected!);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}}"></or-mwc-input>
|
|
260
|
+
` : undefined}
|
|
261
|
+
` : undefined}
|
|
262
|
+
${!this.readonly ? html`
|
|
263
|
+
<or-mwc-input type="${InputType.BUTTON}" class="hideMobile" icon="plus"
|
|
264
|
+
@or-mwc-input-changed="${() => {
|
|
265
|
+
this.createDashboard(DashboardSizeOption.DESKTOP);
|
|
266
|
+
}}"
|
|
267
|
+
></or-mwc-input>
|
|
268
|
+
` : undefined}
|
|
269
|
+
</div>
|
|
270
|
+
` : undefined}
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<!-- List of dashboards -->
|
|
274
|
+
<div id="content">
|
|
275
|
+
<div style="padding-top: 8px;">
|
|
276
|
+
${dashboardItems.map((items, index) => {
|
|
277
|
+
return (items != null && items.length > 0) ? html`
|
|
278
|
+
<div style="padding: 8px 0;">
|
|
279
|
+
<span style="font-weight: 500; padding-left: 14px; color: #000000;">
|
|
280
|
+
<or-translate value="${(index === 0 ? 'dashboard.myDashboards' : 'dashboard.createdByOthers')}"></or-translate>
|
|
281
|
+
</span>
|
|
282
|
+
<div id="list-container" style="overflow: hidden;">
|
|
283
|
+
<ol id="list">
|
|
284
|
+
${items.map((listItem: ListItem) => {
|
|
285
|
+
return html`
|
|
286
|
+
<li ?data-selected="${listItem.value === this.selected?.id}" @click="${(_evt: MouseEvent) => {
|
|
287
|
+
this.onDashboardClick(listItem.value);
|
|
288
|
+
}}">
|
|
289
|
+
<div class="node-container">
|
|
290
|
+
<span class="node-name">${listItem.text} </span>
|
|
291
|
+
</div>
|
|
292
|
+
</li>
|
|
293
|
+
`;
|
|
294
|
+
})}
|
|
295
|
+
</ol>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
` : undefined;
|
|
299
|
+
})}
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {i18next} from "@openremote/or-translate";
|
|
2
|
+
import {html, LitElement, PropertyValues} from "lit";
|
|
3
|
+
import {customElement, property, query, state} from "lit/decorators.js";
|
|
4
|
+
import {when} from "lit/directives/when.js";
|
|
5
|
+
import {throttle} from "lodash";
|
|
6
|
+
import {style} from "./style";
|
|
7
|
+
import {DashboardWidget} from "@openremote/model";
|
|
8
|
+
import {OrWidget, WidgetManifest} from "./util/or-widget";
|
|
9
|
+
import {WidgetService} from "./service/widget-service";
|
|
10
|
+
import {WidgetConfig} from "./util/widget-config";
|
|
11
|
+
|
|
12
|
+
/* ------------------------------------ */
|
|
13
|
+
|
|
14
|
+
const elemTagName = "or-dashboard-widget-container"
|
|
15
|
+
|
|
16
|
+
@customElement(elemTagName)
|
|
17
|
+
export class OrDashboardWidgetContainer extends LitElement {
|
|
18
|
+
|
|
19
|
+
static tagName = elemTagName;
|
|
20
|
+
|
|
21
|
+
@property()
|
|
22
|
+
protected readonly widget!: DashboardWidget;
|
|
23
|
+
|
|
24
|
+
@property()
|
|
25
|
+
protected readonly editMode!: boolean;
|
|
26
|
+
|
|
27
|
+
@property()
|
|
28
|
+
protected loading: boolean = false;
|
|
29
|
+
|
|
30
|
+
@state()
|
|
31
|
+
protected orWidget?: OrWidget
|
|
32
|
+
|
|
33
|
+
@state()
|
|
34
|
+
protected error?: string; // untranslated error messages
|
|
35
|
+
|
|
36
|
+
@query("#widget-container")
|
|
37
|
+
protected containerElem?: Element;
|
|
38
|
+
|
|
39
|
+
protected resizeObserver?: ResizeObserver;
|
|
40
|
+
protected manifest?: WidgetManifest;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
static get styles() {
|
|
44
|
+
return [style];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
disconnectedCallback() {
|
|
48
|
+
super.disconnectedCallback()
|
|
49
|
+
this.resizeObserver?.disconnect();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
shouldUpdate(changedProps: Map<PropertyKey, unknown>): boolean {
|
|
53
|
+
const changed = changedProps;
|
|
54
|
+
|
|
55
|
+
// Update config if some values in the spec are not set.
|
|
56
|
+
// Useful for when migrations have taken place.
|
|
57
|
+
if (this.widget) {
|
|
58
|
+
const manifest = WidgetService.getManifest(this.widget.widgetTypeId!);
|
|
59
|
+
if (manifest) {
|
|
60
|
+
this.widget.widgetConfig = WidgetService.correctToConfigSpec(manifest, this.widget.widgetConfig);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Only update widget if certain properties of widget has changed.
|
|
65
|
+
// For example, when the 'gridItem' field changes, no update is needed since it doesn't apply here.
|
|
66
|
+
if (changedProps.has('widget') && this.widget) {
|
|
67
|
+
const oldVal = changedProps.get('widget') as DashboardWidget | undefined;
|
|
68
|
+
const idChanged = oldVal?.id !== this.widget?.id;
|
|
69
|
+
const nameChanged = oldVal?.displayName !== this.widget?.displayName;
|
|
70
|
+
const configChanged = JSON.stringify(oldVal?.widgetConfig) !== JSON.stringify(this.widget?.widgetConfig);
|
|
71
|
+
if (!(idChanged || nameChanged || configChanged)) {
|
|
72
|
+
changed.delete('widget');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (changed.size === 0 ? false : super.shouldUpdate(changedProps));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
willUpdate(changedProps: Map<string, any>) {
|
|
80
|
+
super.willUpdate(changedProps);
|
|
81
|
+
|
|
82
|
+
if (!this.manifest && this.widget) {
|
|
83
|
+
this.manifest = WidgetService.getManifest(this.widget.widgetTypeId!);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Create widget
|
|
87
|
+
if (changedProps.has("widget") && this.widget) {
|
|
88
|
+
this.initializeWidgetElem(this.manifest!, this.widget.widgetConfig);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
firstUpdated(changedProps: PropertyValues) {
|
|
93
|
+
super.firstUpdated(changedProps);
|
|
94
|
+
|
|
95
|
+
if (this.orWidget) {
|
|
96
|
+
const containerElem = this.containerElem;
|
|
97
|
+
if (containerElem) {
|
|
98
|
+
this.resizeObserver?.disconnect();
|
|
99
|
+
this.resizeObserver = new ResizeObserver(throttle(() => {
|
|
100
|
+
const minWidth = this.manifest!.minPixelWidth || 0;
|
|
101
|
+
const minHeight = this.manifest!.minPixelHeight || 0;
|
|
102
|
+
const isMinimumSize: boolean = (minWidth < containerElem.clientWidth) && (minHeight < containerElem.clientHeight);
|
|
103
|
+
this.error = (isMinimumSize ? undefined : "dashboard.widgetTooSmall");
|
|
104
|
+
}, 200));
|
|
105
|
+
this.resizeObserver.observe(containerElem);
|
|
106
|
+
} else {
|
|
107
|
+
console.error("gridItemElement could not be found!");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected initializeWidgetElem(manifest: WidgetManifest, config: WidgetConfig) {
|
|
113
|
+
console.debug(`Initialising ${manifest.displayName} widget..`);
|
|
114
|
+
if (this.orWidget) {
|
|
115
|
+
this.orWidget.remove();
|
|
116
|
+
}
|
|
117
|
+
this.orWidget = manifest.getContentHtml(config);
|
|
118
|
+
this.orWidget.getDisplayName = () => this.widget.displayName;
|
|
119
|
+
this.orWidget.getEditMode = () => this.editMode
|
|
120
|
+
this.orWidget.getWidgetLocation = () => ({
|
|
121
|
+
x: this.widget.gridItem?.x,
|
|
122
|
+
y: this.widget.gridItem?.y,
|
|
123
|
+
w: this.widget.gridItem?.w,
|
|
124
|
+
h: this.widget.gridItem?.h
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected render() {
|
|
129
|
+
const showHeader = !!this.widget.displayName;
|
|
130
|
+
return html`
|
|
131
|
+
<div id="widget-container" style="height: calc(100% - 16px); padding: 8px 16px 8px 16px; display: flex; flex-direction: column;">
|
|
132
|
+
|
|
133
|
+
<!-- Container title -->
|
|
134
|
+
${when(showHeader, () => html`
|
|
135
|
+
<div style="flex: 0 0 36px; display: flex; justify-content: space-between; align-items: center;">
|
|
136
|
+
<span class="panel-title" style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
137
|
+
${this.widget.displayName?.toUpperCase()}
|
|
138
|
+
</span>
|
|
139
|
+
</div>
|
|
140
|
+
`)}
|
|
141
|
+
|
|
142
|
+
<!-- Content -->
|
|
143
|
+
<div style="flex: 1; max-height: ${showHeader ? 'calc(100% - 36px)' : '100%'};">
|
|
144
|
+
${when((!this.error && !this.loading), () => html`
|
|
145
|
+
${this.orWidget}
|
|
146
|
+
`, () => html`<or-translate value="${this.error ? this.error : "loading"}"></or-translate>`)}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public refreshContent(force: boolean) {
|
|
153
|
+
this.orWidget?.refreshContent(force);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {Asset, DashboardWidget } from "@openremote/model";
|
|
2
|
+
import {html, LitElement, TemplateResult, unsafeCSS } from "lit";
|
|
3
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
4
|
+
import { InputType, OrInputChangedEvent } from "@openremote/or-mwc-components/or-mwc-input";
|
|
5
|
+
import {style} from './style';
|
|
6
|
+
import { i18next } from "@openremote/or-translate";
|
|
7
|
+
import { until } from "lit/directives/until.js";
|
|
8
|
+
import {WidgetService} from "./service/widget-service";
|
|
9
|
+
import {WidgetSettings, WidgetSettingsChangedEvent} from "./util/widget-settings";
|
|
10
|
+
import {WidgetManifest} from "./util/or-widget";
|
|
11
|
+
import { guard } from "lit/directives/guard.js";
|
|
12
|
+
|
|
13
|
+
const tableStyle = require("@material/data-table/dist/mdc.data-table.css");
|
|
14
|
+
|
|
15
|
+
/* ------------------------------------ */
|
|
16
|
+
|
|
17
|
+
@customElement("or-dashboard-widgetsettings")
|
|
18
|
+
export class OrDashboardWidgetsettings extends LitElement {
|
|
19
|
+
|
|
20
|
+
static get styles() {
|
|
21
|
+
return [unsafeCSS(tableStyle), style]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@property({type: Object})
|
|
25
|
+
protected selectedWidget!: DashboardWidget;
|
|
26
|
+
|
|
27
|
+
@state()
|
|
28
|
+
protected settingsElem?: WidgetSettings;
|
|
29
|
+
|
|
30
|
+
/* ------------------------------------ */
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
// Method to update the Grid. For example after changing a setting.
|
|
34
|
+
forceParentUpdate(changes: Map<string, any>, force: boolean = false) {
|
|
35
|
+
this.requestUpdate();
|
|
36
|
+
this.dispatchEvent(new CustomEvent('update', { detail: { changes: changes, force: force }}));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected render() {
|
|
40
|
+
return html`
|
|
41
|
+
<div style="padding: 12px;">
|
|
42
|
+
<div>
|
|
43
|
+
<or-mwc-input .type="${InputType.TEXT}" style="width: 100%;" .value="${this.selectedWidget?.displayName}" label="${i18next.t('name')}"
|
|
44
|
+
@or-mwc-input-changed="${(event: OrInputChangedEvent) => this.setDisplayName(event.detail.value)}"
|
|
45
|
+
></or-mwc-input>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div>
|
|
49
|
+
${guard([this.selectedWidget], () => html`
|
|
50
|
+
${until(this.generateContent(this.selectedWidget!.widgetTypeId!), html`Loading...`)}
|
|
51
|
+
`)}
|
|
52
|
+
</div>
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected setDisplayName(name?: string) {
|
|
57
|
+
this.selectedWidget!.displayName = name;
|
|
58
|
+
this.forceParentUpdate(new Map<string, any>([['widget', this.selectedWidget]]));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected async generateContent(widgetTypeId: string): Promise<TemplateResult> {
|
|
62
|
+
if(!this.settingsElem || this.settingsElem.id !== this.selectedWidget.id) {
|
|
63
|
+
const manifest = WidgetService.getManifest(widgetTypeId);
|
|
64
|
+
this.settingsElem = this.initSettings(manifest);
|
|
65
|
+
}
|
|
66
|
+
return html`
|
|
67
|
+
${this.settingsElem}
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
protected initSettings(manifest: WidgetManifest): WidgetSettings {
|
|
72
|
+
const settingsElem = manifest.getSettingsHtml(this.selectedWidget!.widgetConfig);
|
|
73
|
+
settingsElem.id = this.selectedWidget.id!;
|
|
74
|
+
settingsElem.getDisplayName = () => this.selectedWidget.displayName;
|
|
75
|
+
settingsElem.setDisplayName = (name?: string) => this.setDisplayName(name);
|
|
76
|
+
settingsElem.getEditMode = () => true;
|
|
77
|
+
settingsElem.getWidgetLocation = () => ({
|
|
78
|
+
x: this.selectedWidget.gridItem?.x,
|
|
79
|
+
y: this.selectedWidget.gridItem?.y,
|
|
80
|
+
w: this.selectedWidget.gridItem?.w,
|
|
81
|
+
h: this.selectedWidget.gridItem?.h
|
|
82
|
+
});
|
|
83
|
+
settingsElem.addEventListener(WidgetSettingsChangedEvent.NAME, (ev: any) => this.onWidgetConfigChange(ev));
|
|
84
|
+
return settingsElem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected onWidgetConfigChange(ev: WidgetSettingsChangedEvent) {
|
|
88
|
+
this.selectedWidget!.widgetConfig = ev.detail;
|
|
89
|
+
this.forceParentUpdate(new Map<string, any>([['widget', this.selectedWidget]]));
|
|
90
|
+
}
|
|
91
|
+
}
|