+
+ );
}
-}
+};
export default App;
diff --git a/src/branding/branding.js b/src/branding/branding.js
index 2d3bf535..82cf8dc8 100644
--- a/src/branding/branding.js
+++ b/src/branding/branding.js
@@ -15,17 +15,29 @@
* along with VILLASweb. If not, see .
******************************************************************************/
-import { villasweb_footer, villasweb_home, villasweb_welcome } from './villasweb/villasweb-functions';
-import villasweb_values from './villasweb/villasweb-values';
-
-import { slew_home, slew_welcome } from './slew/slew-functions';
-import slew_values from './slew/slew-values';
-
-import { opalrt_footer, opalrt_home, opalrt_welcome } from './opalrt/opalrt-functions';
-import opalrt_values from './opalrt/opalrt-values';
-
-import { template_welcome, template_home, template_footer } from './template/template-functions';
-import template_values from './template/template-values';
+import {
+ villasweb_footer,
+ villasweb_home,
+ villasweb_welcome,
+} from "./villasweb/villasweb-functions";
+import villasweb_values from "./villasweb/villasweb-values";
+
+import { slew_home, slew_welcome } from "./slew/slew-functions";
+import slew_values from "./slew/slew-values";
+
+import {
+ opalrt_footer,
+ opalrt_home,
+ opalrt_welcome,
+} from "./opalrt/opalrt-functions";
+import opalrt_values from "./opalrt/opalrt-values";
+
+import {
+ template_welcome,
+ template_home,
+ template_footer,
+} from "./template/template-functions";
+import template_values from "./template/template-values";
class Branding {
constructor(brand) {
@@ -40,39 +52,43 @@ class Branding {
setValues() {
switch (this.brand) {
- case 'villasweb':
+ case "villasweb":
this.values = villasweb_values;
break;
- case 'slew':
+ case "slew":
this.values = slew_values;
break;
- case 'opalrt':
+ case "opalrt":
this.values = opalrt_values;
break;
- case 'template':
+ case "template":
this.values = template_values;
break;
default:
- console.error("Branding '" + this.brand + "' not available, will use 'villasweb' branding");
- this.brand = 'villasweb';
+ console.error(
+ "Branding '" +
+ this.brand +
+ "' not available, will use 'villasweb' branding"
+ );
+ this.brand = "villasweb";
this.values = villasweb_values;
break;
}
}
- getHome(username = '', userid = '', role = '') {
- var homepage = '';
+ getHome(username = "", userid = "", role = "") {
+ var homepage = "";
switch (this.brand) {
- case 'villasweb':
+ case "villasweb":
homepage = villasweb_home(this.getTitle(), username, userid, role);
break;
- case 'slew':
+ case "slew":
homepage = slew_home();
break;
- case 'opalrt':
+ case "opalrt":
homepage = opalrt_home(this.getTitle(), username, userid, role);
break;
- case 'template':
+ case "template":
homepage = template_home();
break;
default:
@@ -83,14 +99,17 @@ class Branding {
}
getFooter() {
- var footer = '';
+ var footer = "";
switch (this.brand) {
- case 'template':
+ case "template":
footer = template_footer();
break;
- case 'opalrt':
+ case "opalrt":
footer = opalrt_footer();
break;
+ case 'enershare':
+ footer = enershare_footer();
+ break;
default:
footer = villasweb_footer();
break;
@@ -99,18 +118,18 @@ class Branding {
}
getWelcome() {
- var welcome = '';
+ var welcome = "";
switch (this.brand) {
- case 'villasweb':
+ case "villasweb":
welcome = villasweb_welcome();
break;
- case 'slew':
+ case "slew":
welcome = slew_welcome();
break;
- case 'opalrt':
+ case "opalrt":
welcome = opalrt_welcome();
break;
- case 'template':
+ case "template":
welcome = template_welcome();
break;
default:
@@ -121,7 +140,12 @@ class Branding {
}
defaultWelcome() {
- return (
Welcome!
This is the welcome page and you are very welcome here.
);
+ return (
+
+
Welcome!
+
This is the welcome page and you are very welcome here.
+
+ );
}
// if icon cannot be found, the default favicon will be used
@@ -137,12 +161,12 @@ class Branding {
if (!this.values.icon) {
return;
}
- var oldlink = document.getElementById('dynamic-favicon');
+ var oldlink = document.getElementById("dynamic-favicon");
- var link = document.createElement('link');
- link.id = 'dynamic-favicon';
- link.rel = 'shortcut icon'
- link.href = '/' + this.values.icon;
+ var link = document.createElement("link");
+ link.id = "dynamic-favicon";
+ link.rel = "shortcut icon";
+ link.href = "/" + this.values.icon;
if (oldlink) {
document.head.removeChild(oldlink);
@@ -151,7 +175,7 @@ class Branding {
}
checkValues() {
- if (!this.values.hasOwnProperty('pages')) {
+ if (!this.values.hasOwnProperty("pages")) {
let pages = {};
pages.home = true;
pages.scenarios = true;
@@ -162,23 +186,23 @@ class Branding {
this.values.pages = pages;
} else {
- if (!this.values.pages.hasOwnProperty('home')) {
- this.values.pages['home'] = false;
+ if (!this.values.pages.hasOwnProperty("home")) {
+ this.values.pages["home"] = false;
}
- if (!this.values.pages.hasOwnProperty('scenarios')) {
- this.values.pages['scenarios'] = false;
+ if (!this.values.pages.hasOwnProperty("scenarios")) {
+ this.values.pages["scenarios"] = false;
}
- if (!this.values.pages.hasOwnProperty('infrastructure')) {
- this.values.pages['infrastructure'] = false;
+ if (!this.values.pages.hasOwnProperty("infrastructure")) {
+ this.values.pages["infrastructure"] = false;
}
- if (!this.values.pages.hasOwnProperty('users')) {
- this.values.pages['users'] = false;
+ if (!this.values.pages.hasOwnProperty("users")) {
+ this.values.pages["users"] = false;
}
- if (!this.values.pages.hasOwnProperty('account')) {
- this.values.pages['account'] = false;
+ if (!this.values.pages.hasOwnProperty("account")) {
+ this.values.pages["account"] = false;
}
- if (!this.values.pages.hasOwnProperty('api')) {
- this.values.pages['api'] = false;
+ if (!this.values.pages.hasOwnProperty("api")) {
+ this.values.pages["api"] = false;
}
}
}
@@ -186,10 +210,10 @@ class Branding {
applyStyle() {
this.changeHead();
- const rootEl = document.querySelector(':root');
+ const rootEl = document.querySelector(":root");
for (const [key, value] of Object.entries(this.values.style)) {
- rootEl.style.setProperty('--' + key, value);
+ rootEl.style.setProperty("--" + key, value);
}
}
@@ -197,9 +221,17 @@ class Branding {
let image = null;
try {
- image =
+ image = (
+
+ );
} catch (err) {
- console.error("cannot find './" + this.brand + '/img/' + this.values.logo + "'");
+ console.error(
+ "cannot find './" + this.brand + "/img/" + this.values.logo + "'"
+ );
}
return image;
@@ -212,7 +244,7 @@ class Branding {
getSubtitle() {
return this.values.subtitle ? this.values.subtitle : null;
}
-};
+}
var branding = new Branding(process.env.REACT_APP_BRAND);
diff --git a/src/branding/enershare/enershare-functions.js b/src/branding/enershare/enershare-functions.js
new file mode 100644
index 00000000..aee263d8
--- /dev/null
+++ b/src/branding/enershare/enershare-functions.js
@@ -0,0 +1,57 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see .
+ ******************************************************************************/
+import React from 'react';
+
+
+export function enershare_home() {
+ return (
+
+
+
Home
+
+ Welcome to DT-FPEN!
+
+
The DT concept for electrical networks is based on the simulation tool DPSim, and allows the interconnection
+ to data acquisition devices (measurement devices and status indicators) and the interaction with other systems.
+
+
It is possibile to interconnect with other systems, like the service for Data-driven management of surplus RES generation
+ where forecasts for the consumers in low voltage are calculated, or that can be used for predicting the consumption
+ of the water pumps in the circuit of the pilot. This will allow the pilot to improve their usage of the flexibility capabilities.
This is the Digital Twin for flexible energy networks, a system designed to facilitate the integration and flexibility on electrical networks with renewable energy sources.
+
)
+}
+
+export function enershare_footer() {
+ return (
+
+ );
+}
diff --git a/src/branding/enershare/enershare-values.js b/src/branding/enershare/enershare-values.js
new file mode 100644
index 00000000..3df93bd8
--- /dev/null
+++ b/src/branding/enershare/enershare-values.js
@@ -0,0 +1,45 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see .
+ ******************************************************************************/
+
+const enershare_values = {
+ title: 'Digital Twin for flexible energy networks',
+ subtitle: '',
+ icon: "logo_Enershare_Icon.svg",
+ logo: "logo_Enershare.svg",
+ pages: {
+ home: true,
+ scenarios: true,
+ infrastructure: true,
+ users: true,
+ account: true,
+ api: true
+ },
+ links: {
+ "AppStore": "https://store.haslab-dataspace.pt/gui/",
+ "The Project": "https://enershare.eu/"
+ },
+ style: {
+ background: 'rgba(24,229,176, 0.6)',
+ highlights: 'rgba(153,102,255, 0.75)',
+ main: 'rgba(0,0,0, 1)',
+ secondaryText: 'rgba(0,0,100, 0.8)',
+ fontFamily: "Manrope, sans-serif",
+ borderRadius: "60px"
+ }
+}
+
+export default enershare_values;
diff --git a/src/branding/enershare/img/logo_Enershare.svg b/src/branding/enershare/img/logo_Enershare.svg
new file mode 100644
index 00000000..439ca4b0
--- /dev/null
+++ b/src/branding/enershare/img/logo_Enershare.svg
@@ -0,0 +1,33 @@
+
+
\ No newline at end of file
diff --git a/src/branding/enershare/img/logo_Enershare_Icon.svg b/src/branding/enershare/img/logo_Enershare_Icon.svg
new file mode 100644
index 00000000..05622f43
--- /dev/null
+++ b/src/branding/enershare/img/logo_Enershare_Icon.svg
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/src/common/api/websocket-api.js b/src/common/api/websocket-api.js
index 210181af..6c33b642 100644
--- a/src/common/api/websocket-api.js
+++ b/src/common/api/websocket-api.js
@@ -24,14 +24,14 @@ class WebSocketManager {
}
connect(id, url, onMessage, onOpen, onClose) {
- const existingSocket = this.sockets.find(s => s.id === id);
+ const existingSocket = this.sockets.find((s) => s.id === id);
if (existingSocket && existingSocket.socket.readyState === WebSocket.OPEN) {
- console.log('Already connected to:', url);
+ console.log("Already connected to:", url);
return;
}
- const socket = new WebSocket(url, 'live');
- socket.binaryType = 'arraybuffer';
+ const socket = new WebSocket(url, "live");
+ socket.binaryType = "arraybuffer";
socket.onopen = onOpen;
socket.onmessage = (event) => {
@@ -45,21 +45,21 @@ class WebSocketManager {
}
disconnect(id) {
- const socket = this.sockets.find(s => s.id === id);
+ const socket = this.sockets.find((s) => s.id === id);
if (socket) {
socket.socket.close();
- this.sockets = this.sockets.filter(s => s.id !== id);
- console.log('WebSocket connection closed for id:', id);
+ this.sockets = this.sockets.filter((s) => s.id !== id);
+ console.log("WebSocket connection closed for id:", id);
}
}
send(id, message) {
- const socket = this.sockets.find(s => s.id === id);
+ const socket = this.sockets.find((s) => s.id === id);
if (socket == null) {
return false;
}
- console.log("Sending to IC", id, "message: ", message);
const data = this.messageToBuffer(message);
+ console.log("📤 Sending binary buffer to server:", message.values);
socket.socket.send(data);
return true;
@@ -70,11 +70,11 @@ class WebSocketManager {
const view = new DataView(buffer);
let bits = 0;
- bits |= (message.version & 0xF) << OFFSET_VERSION;
+ bits |= (message.version & 0xf) << OFFSET_VERSION;
bits |= (message.type & 0x3) << OFFSET_TYPE;
let source_index = 0;
- source_index |= (message.source_index & 0xFF);
+ source_index |= message.source_index & 0xff;
const sec = Math.floor(message.timestamp / 1e3);
const nsec = (message.timestamp - sec * 1e3) * 1e6;
@@ -84,7 +84,7 @@ class WebSocketManager {
view.setUint16(0x02, message.length, true);
view.setUint32(0x04, message.sequence, true);
view.setUint32(0x08, sec, true);
- view.setUint32(0x0C, nsec, true);
+ view.setUint32(0x0c, nsec, true);
const data = new Float32Array(buffer, 0x10, message.length);
data.set(message.values);
@@ -119,19 +119,19 @@ class WebSocketManager {
const bytes = length * 4 + 16;
return {
- version: (bits >> OFFSET_VERSION) & 0xF,
+ version: (bits >> OFFSET_VERSION) & 0xf,
type: (bits >> OFFSET_TYPE) & 0x3,
source_index: source_index,
length: length,
sequence: data.getUint32(0x04, 1),
- timestamp: data.getUint32(0x08, 1) * 1e3 + data.getUint32(0x0C, 1) * 1e-6,
+ timestamp: data.getUint32(0x08, 1) * 1e3 + data.getUint32(0x0c, 1) * 1e-6,
values: new Float32Array(data.buffer, data.byteOffset + 0x10, length),
blob: new DataView(data.buffer, data.byteOffset + 0x00, bytes),
};
}
getSocketById(id) {
- const socketEntry = this.sockets.find(s => s.id === id);
+ const socketEntry = this.sockets.find((s) => s.id === id);
return socketEntry ? socketEntry.socket : null;
}
diff --git a/src/pages/dashboards/dashboard-button-group.js b/src/pages/dashboards/dashboard-button-group.js
deleted file mode 100644
index 04a1500d..00000000
--- a/src/pages/dashboards/dashboard-button-group.js
+++ /dev/null
@@ -1,112 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see .
- ******************************************************************************/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import IconButton from '../../common/buttons/icon-button';
-
-const buttonStyle = {
- marginLeft: '12px',
- height: '44px',
- width: '35px',
-};
-
-const iconStyle = {
- height: '25px',
- width: '25px'
-};
-
-let buttonkey = 0;
-
-class DashboardButtonGroup extends React.Component {
-
- getBtn(icon, tooltip, clickFn, locked = false) {
- if (locked) {
- return
- } else {
- return
- }
- }
-
- render() {
- const buttons = [];
- buttonkey = 0;
-
- if (this.props.editing) {
- buttons.push(this.getBtn("save", "Save changes", this.props.onSave));
- buttons.push(this.getBtn("times", "Discard changes", this.props.onCancel));
- } else {
- if (this.props.fullscreen !== true) {
- buttons.push(this.getBtn("expand", "Change to fullscreen view", this.props.onFullscreen));
- } else {
- buttons.push(this.getBtn("compress", "Back to normal view", this.props.onFullscreen));
- }
-
- if (this.props.paused) {
- buttons.push(this.getBtn("play", "Continue simulation", this.props.onUnpause));
- } else {
- buttons.push(this.getBtn("pause", "Pause simulation", this.props.onPause));
- }
-
- if (this.props.fullscreen !== true) {
- let tooltip = this.props.locked ? "View files of scenario" : "Add, edit or delete files of scenario";
- buttons.push(this.getBtn("file", tooltip, this.props.onEditFiles));
- buttons.push(this.getBtn("sign-in-alt", "Add, edit or delete input signals", this.props.onEditInputSignals, this.props.locked));
- buttons.push(this.getBtn("sign-out-alt", "Add, edit or delete output signals", this.props.onEditOutputSignals, this.props.locked));
- buttons.push(this.getBtn("pen", "Add widgets and edit layout", this.props.onEdit, this.props.locked));
- }
- }
-
- return
+ );
+};
+
+export default Fullscreenable()(Dashboard);
diff --git a/src/pages/dashboards/dialogs/edit-file-content.js b/src/pages/dashboards/dialogs/edit-file-content.js
deleted file mode 100644
index c14570da..00000000
--- a/src/pages/dashboards/dialogs/edit-file-content.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see .
- ******************************************************************************/
-
-import React from 'react';
-import { Form, Button, Col } from 'react-bootstrap';
-import Dialog from '../../../common/dialogs/dialog';
-
-class EditFileContent extends React.Component {
- valid = true;
-
- constructor(props) {
- super(props);
-
- this.state = {
- uploadFile: null,
- };
- }
-
- selectUploadFile(event) {
- this.setState({ uploadFile: event.target.files[0] });
- };
-
- startEditContent(){
- const formData = new FormData();
- formData.append("file", this.state.uploadFile);
-
- AppDispatcher.dispatch({
- type: 'files/start-edit',
- data: formData,
- token: this.props.sessionToken,
- id: this.props.file.id
- });
-
- this.setState({ uploadFile: null });
- };
-
- onClose = () => {
- this.props.onClose();
- };
-
- render() {
- return ;
- }
-}
-
-export default EditFileContent;
\ No newline at end of file
diff --git a/src/pages/dashboards/dialogs/edit-file-content.jsx b/src/pages/dashboards/dialogs/edit-file-content.jsx
new file mode 100644
index 00000000..d99857bb
--- /dev/null
+++ b/src/pages/dashboards/dialogs/edit-file-content.jsx
@@ -0,0 +1,57 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see .
+ ******************************************************************************/
+import React, { useState } from "react";
+import { Form, Button, Col } from "react-bootstrap";
+import Dialog from "../../../common/dialogs/dialog";
+
+const EditFileContent = ({ show, onClose, file, updateFile }) => {
+ const [uploadFile, setUploadFile] = useState(null);
+
+ const selectUploadFile = (event) => {
+ const selectedFile = event.target.files[0];
+ setUploadFile(selectedFile);
+ };
+
+ const handleUploadFile = () => {
+ updateFile(file.id, uploadFile);
+ setUploadFile(null);
+ onClose();
+ };
+
+ return (
+
+ );
+};
+
+export default EditFileContent;
diff --git a/src/pages/dashboards/dialogs/edit-files-dialog.js b/src/pages/dashboards/dialogs/edit-files-dialog.js
deleted file mode 100644
index 3eacda4e..00000000
--- a/src/pages/dashboards/dialogs/edit-files-dialog.js
+++ /dev/null
@@ -1,192 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see .
- ******************************************************************************/
-
-import React from 'react';
-import {Form, Button, Col, ProgressBar, Row} from 'react-bootstrap';
-import Dialog from '../../../common/dialogs/dialog';
-import { Table, ButtonColumn, DataColumn } from "../../../common/table";
-import EditFileContent from "./edit-file-content";
-
-class EditFilesDialog extends React.Component {
- valid = true;
-
- constructor(props) {
- super(props);
-
- this.state = {
- uploadFile: null,
- uploadProgress: 0,
- editModal: false,
- modalFile: {}
- };
- }
-
- onClose() {
- this.props.onClose();
- }
-
- selectUploadFile(event) {
- this.setState({ uploadFile: event.target.files[0] });
- };
-
- startFileUpload(){
- // upload file
- const formData = new FormData();
- formData.append("file", this.state.uploadFile);
-
- AppDispatcher.dispatch({
- type: 'files/start-upload',
- data: formData,
- token: this.props.sessionToken,
- progressCallback: this.updateUploadProgress,
- finishedCallback: this.clearProgress,
- scenarioID: this.props.scenarioID,
- });
-
- this.setState({ uploadFile: null });
- };
-
- updateUploadProgress = (event) => {
- if (event.hasOwnProperty("percent")){
- this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) });
- } else {
- this.setState({ uploadProgress: 0 });
- }
- };
-
- clearProgress = (newFileID) => {
- this.setState({ uploadProgress: 0 });
- };
-
- closeEditModal() {
- this.setState({editModal: false});
- }
-
- deleteFile(index){
- let file = this.props.files[index]
- AppDispatcher.dispatch({
- type: 'files/start-remove',
- data: file,
- token: this.props.sessionToken
- });
- }
-
- render() {
- let fileOptions = [];
- if (this.props.files.length > 0){
- fileOptions.push(
-
- )
- fileOptions.push(this.props.files.map((file, index) => (
-
- )))
- } else {
- fileOptions =
- }
-
- const progressBarStyle = {
- marginLeft: '100px',
- marginTop: '-40px'
- };
-
- let title = this.props.locked ? "View files of scenario" : "Edit Files of Scenario";
-
- return (
-
- );
- }
-}
-
-export default EditFilesDialog;
\ No newline at end of file
diff --git a/src/pages/dashboards/dialogs/edit-files-dialog.jsx b/src/pages/dashboards/dialogs/edit-files-dialog.jsx
new file mode 100644
index 00000000..d8fae870
--- /dev/null
+++ b/src/pages/dashboards/dialogs/edit-files-dialog.jsx
@@ -0,0 +1,111 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see .
+ ******************************************************************************/
+
+import React, { useState } from "react";
+import { Form, Button, Col, ProgressBar, Row } from "react-bootstrap";
+import Dialog from "../../../common/dialogs/dialog";
+import { Table, ButtonColumn, DataColumn } from "../../../common/table";
+import EditFileContent from "./edit-file-content.jsx";
+
+const EditFilesDialog = (props) => {
+ const [uploadFile, setUploadFile] = useState(null);
+ const [uploadProgress, setUploadProgress] = useState(0);
+ const [editModal, setEditModal] = useState(false);
+ const [modalFile, setModalFile] = useState({});
+
+ const onClose = () => {
+ props.onClose();
+ };
+
+ const selectUploadFile = (e) => {
+ console.log("SELECTED FILE", e.target.files[0]);
+ setUploadFile(e.target.files[0]);
+ };
+
+ let title = props.locked
+ ? "View files of scenario"
+ : "Edit Files of Scenario";
+
+ return (
+
+ );
+};
+
+export default EditFilesDialog;
diff --git a/src/pages/dashboards/dialogs/edit-signal-mapping.jsx b/src/pages/dashboards/dialogs/edit-signal-mapping.jsx
new file mode 100644
index 00000000..ecc77d36
--- /dev/null
+++ b/src/pages/dashboards/dialogs/edit-signal-mapping.jsx
@@ -0,0 +1,273 @@
+import { useState, useEffect } from "react";
+import { Button, Form, OverlayTrigger, Tooltip } from "react-bootstrap";
+import {
+ Table,
+ ButtonColumn,
+ CheckboxColumn,
+ DataColumn,
+} from "../../../common/table";
+import {
+ dialogWarningLabel,
+ signalDialogCheckButton,
+ buttonStyle,
+} from "../styles";
+import Dialog from "../../../common/dialogs/dialog";
+import Icon from "../../../common/icon";
+import {
+ useDeleteSignalMutation,
+ useUpdateSignalMutation,
+} from "../../../store/apiSlice";
+import { Collapse } from "react-collapse";
+
+const EditSignalMappingDialog = ({
+ show,
+ direction,
+ onClose,
+ signals,
+ refetch,
+}) => {
+ const [isCollapseOpened, setCollapseOpened] = useState(false);
+ const [checkedSignalsIDs, setCheckedSignalsIDs] = useState([]);
+ const [deleteSignal] = useDeleteSignalMutation();
+ const [updateSignal] = useUpdateSignalMutation();
+
+ const [updatedSignals, setUpdatedSignals] = useState([]);
+ const [updatedSignalsIDs, setUpdatedSignalsIDs] = useState([]);
+
+ useEffect(() => {
+ if (signals.length > 0) {
+ setUpdatedSignals([...signals]);
+ }
+ }, [signals]);
+
+ const handleMappingChange = (e, row, column) => {
+ const signalToUpdate = { ...updatedSignals[row] };
+ switch (column) {
+ case 1:
+ signalToUpdate.index = e.target.value;
+ break;
+ case 2:
+ signalToUpdate.name = e.target.value;
+ break;
+ case 3:
+ signalToUpdate.unit = e.target.value;
+ break;
+ case 4:
+ signalToUpdate.scalingFactor = e.target.value;
+ break;
+ default:
+ break;
+ }
+
+ setUpdatedSignals((prevState) =>
+ prevState.map((signal, index) =>
+ index === row ? signalToUpdate : signal
+ )
+ );
+
+ setUpdatedSignalsIDs((prevState) => [signalToUpdate.id, ...prevState]);
+ };
+
+ const handleDelete = async (signalID) => {
+ try {
+ await deleteSignal(signalID).unwrap();
+ } catch (err) {
+ console.log(err);
+ }
+
+ refetch();
+ };
+
+ const handleUpdate = async () => {
+ try {
+ for (let id of updatedSignalsIDs) {
+ const signalToUpdate = updatedSignals.find(
+ (signal) => signal.id === id
+ );
+
+ if (signalToUpdate) {
+ await updateSignal({
+ signalID: id,
+ updatedSignal: signalToUpdate,
+ }).unwrap();
+ }
+ }
+
+ refetch();
+ setUpdatedSignalsIDs([]);
+ } catch (error) {
+ console.error("Error updating signals:", error);
+ }
+ };
+
+ const onSignalChecked = (signal, event) => {
+ if (!checkedSignalsIDs.includes(signal.id)) {
+ setCheckedSignalsIDs((prevState) => [...prevState, signal.id]);
+ } else {
+ const index = checkedSignalsIDs.indexOf(signal.id);
+ setCheckedSignalsIDs((prevState) =>
+ prevState.filter((_, i) => i !== index)
+ );
+ }
+ };
+
+ const isSignalChecked = (signal) => {
+ return checkedSignalsIDs.includes(signal.id);
+ };
+
+ const toggleCheckAll = () => {
+ //check if all signals are already checked
+ if (checkedSignalsIDs.length === signals.length) {
+ setCheckedSignalsIDs([]);
+ } else {
+ signals.forEach((signal) => {
+ if (!checkedSignalsIDs.includes(signal.id)) {
+ setCheckedSignalsIDs((prevState) => [...prevState, signal.id]);
+ }
+ });
+ }
+ };
+
+ const deleteCheckedSignals = async () => {
+ if (checkedSignalsIDs.length > 0) {
+ try {
+ const deletePromises = checkedSignalsIDs.map((signalID) =>
+ deleteSignal(signalID).unwrap()
+ );
+ await Promise.all(deletePromises);
+ refetchSignals();
+ } catch (err) {
+ console.log(err);
+ }
+ }
+ };
+
+ const DialogWindow = (
+
+ );
+
+ return show ? DialogWindow : <>>;
+};
+
+export default EditSignalMappingDialog;
diff --git a/src/pages/dashboards/grid/dashboard-button-group.js b/src/pages/dashboards/grid/dashboard-button-group.js
new file mode 100644
index 00000000..e97536ed
--- /dev/null
+++ b/src/pages/dashboards/grid/dashboard-button-group.js
@@ -0,0 +1,154 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see .
+ ******************************************************************************/
+
+import React from "react";
+import PropTypes from "prop-types";
+import IconButton from "../../../common/buttons/icon-button";
+
+const buttonStyle = {
+ marginLeft: "12px",
+ height: "44px",
+ width: "35px",
+};
+
+const iconStyle = {
+ height: "25px",
+ width: "25px",
+};
+
+let buttonkey = 0;
+
+class DashboardButtonGroup extends React.Component {
+ getBtn(icon, tooltip, clickFn, locked = false) {
+ if (locked) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ }
+
+ render() {
+ const buttons = [];
+ buttonkey = 0;
+
+ if (this.props.editing) {
+ buttons.push(this.getBtn("save", "Save changes", this.props.onSave));
+ buttons.push(
+ this.getBtn("times", "Discard changes", this.props.onCancel)
+ );
+ } else {
+ if (this.props.fullscreen !== true) {
+ buttons.push(
+ this.getBtn(
+ "expand",
+ "Change to fullscreen view",
+ this.props.onFullscreen
+ )
+ );
+ } else {
+ buttons.push(
+ this.getBtn(
+ "compress",
+ "Back to normal view",
+ this.props.onFullscreen
+ )
+ );
+ }
+
+ if (this.props.paused) {
+ buttons.push(
+ this.getBtn("play", "Continue simulation", this.props.onUnpause)
+ );
+ } else {
+ buttons.push(
+ this.getBtn("pause", "Pause simulation", this.props.onPause)
+ );
+ }
+
+ if (this.props.fullscreen !== true) {
+ let tooltip = this.props.locked
+ ? "View files of scenario"
+ : "Add, edit or delete files of scenario";
+ buttons.push(this.getBtn("file", tooltip, this.props.onEditFiles));
+ buttons.push(
+ this.getBtn(
+ "sign-in-alt",
+ "Add, edit or delete input signals",
+ this.props.onEditInputSignals,
+ this.props.locked
+ )
+ );
+ buttons.push(
+ this.getBtn(
+ "sign-out-alt",
+ "Add, edit or delete output signals",
+ this.props.onEditOutputSignals,
+ this.props.locked
+ )
+ );
+ buttons.push(
+ this.getBtn(
+ "pen",
+ "Add widgets and edit layout",
+ this.props.onEdit,
+ this.props.locked
+ )
+ );
+ }
+ }
+
+ return
{buttons}
;
+ }
+}
+
+DashboardButtonGroup.propTypes = {
+ editing: PropTypes.bool,
+ fullscreen: PropTypes.bool,
+ paused: PropTypes.bool,
+ onEdit: PropTypes.func,
+ onSave: PropTypes.func,
+ onCancel: PropTypes.func,
+ onFullscreen: PropTypes.func,
+ onPause: PropTypes.func,
+ onUnpause: PropTypes.func,
+};
+
+export default DashboardButtonGroup;
diff --git a/src/pages/dashboards/dropzone.js b/src/pages/dashboards/grid/dropzone.js
similarity index 100%
rename from src/pages/dashboards/dropzone.js
rename to src/pages/dashboards/grid/dropzone.js
diff --git a/src/pages/dashboards/grid.js b/src/pages/dashboards/grid/grid.js
similarity index 100%
rename from src/pages/dashboards/grid.js
rename to src/pages/dashboards/grid/grid.js
diff --git a/src/pages/dashboards/widget-area.js b/src/pages/dashboards/grid/widget-area.js
similarity index 71%
rename from src/pages/dashboards/widget-area.js
rename to src/pages/dashboards/grid/widget-area.js
index a70fd9c0..63a58910 100644
--- a/src/pages/dashboards/widget-area.js
+++ b/src/pages/dashboards/grid/widget-area.js
@@ -15,11 +15,11 @@
* along with VILLASweb. If not, see .
******************************************************************************/
-import React from 'react';
-import PropTypes from 'prop-types';
-import Dropzone from './dropzone';
-import Grid from './grid';
-import WidgetFactory from './widget/widget-factory';
+import React from "react";
+import PropTypes from "prop-types";
+import Dropzone from "./dropzone";
+import Grid from "./grid";
+import WidgetFactory from "../widget/widget-factory";
class WidgetArea extends React.Component {
snapToGrid(value) {
@@ -39,23 +39,24 @@ class WidgetArea extends React.Component {
if (this.props.onWidgetAdded != null) {
this.props.onWidgetAdded(widget);
}
- }
+ };
render() {
+ return (
+
+ {this.props.children}
- return
- {this.props.children}
-
-
- ;
+
+
+ );
}
}
@@ -64,11 +65,11 @@ WidgetArea.propTypes = {
editing: PropTypes.bool,
grid: PropTypes.number,
//widgets: PropTypes.array,
- onWidgetAdded: PropTypes.func
+ onWidgetAdded: PropTypes.func,
};
WidgetArea.defaultProps = {
- widgets: {}
+ widgets: {},
};
export default WidgetArea;
diff --git a/src/pages/dashboards/hooks/use-dashboard-data.js b/src/pages/dashboards/hooks/use-dashboard-data.js
new file mode 100644
index 00000000..6734dd0c
--- /dev/null
+++ b/src/pages/dashboards/hooks/use-dashboard-data.js
@@ -0,0 +1,102 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see .
+ ******************************************************************************/
+
+import { useState, useEffect, useMemo } from "react";
+import {
+ useLazyGetFilesQuery,
+ useLazyGetConfigsQuery,
+ useLazyGetSignalsQuery,
+ useGetICSQuery,
+} from "../../../store/apiSlice";
+
+// a custom hook designed to deliver data required for a dashboard and its widgets
+export const useDashboardData = (scenarioID) => {
+ const [files, setFiles] = useState([]);
+ const [configs, setConfigs] = useState([]);
+ const [signals, setSignals] = useState([]);
+ const [activeICS, setActiveICS] = useState([]);
+ const { data: { ics } = { ics: [] } } = useGetICSQuery();
+
+ const [triggerGetFiles] = useLazyGetFilesQuery();
+ const [triggerGetConfigs] = useLazyGetConfigsQuery();
+ const [triggerGetSignals] = useLazyGetSignalsQuery();
+
+ const fetchDashboardData = async () => {
+ if (!scenarioID) return;
+
+ //in case of refetching
+ setFiles([]);
+ setConfigs([]);
+ setSignals([]);
+
+ try {
+ // Fetch files
+ const filesRes = await triggerGetFiles(scenarioID).unwrap();
+ if (filesRes?.files) {
+ setFiles(filesRes.files);
+ }
+
+ // Fetch configs and signals
+ const configsRes = await triggerGetConfigs(scenarioID).unwrap();
+ if (configsRes?.configs) {
+ setConfigs(configsRes.configs);
+
+ for (const config of configsRes.configs) {
+ const signalsInRes = await triggerGetSignals({
+ configID: config.id,
+ direction: "in",
+ }).unwrap();
+ const signalsOutRes = await triggerGetSignals({
+ configID: config.id,
+ direction: "out",
+ }).unwrap();
+
+ setSignals((prev) => [
+ ...prev,
+ ...signalsInRes.signals,
+ ...signalsOutRes.signals,
+ ]);
+ }
+ }
+ } catch (err) {
+ console.error("Error fetching dashboard data:", err);
+ }
+ };
+
+ useEffect(() => {
+ if (scenarioID) {
+ fetchDashboardData();
+ }
+ }, [scenarioID]);
+
+ // Derive active ICS based on the fetched configs
+ useEffect(() => {
+ let usedICS = [];
+ for (const config of configs) {
+ usedICS.push(config.icID);
+ }
+ setActiveICS(ics.filter((i) => usedICS.includes(i.id)));
+ }, [configs]);
+
+ return {
+ files,
+ configs,
+ signals,
+ activeICS,
+ refetchDashboardData: fetchDashboardData,
+ };
+};
diff --git a/src/pages/dashboards/hooks/use-websocket-connection.js b/src/pages/dashboards/hooks/use-websocket-connection.js
new file mode 100644
index 00000000..30a9c357
--- /dev/null
+++ b/src/pages/dashboards/hooks/use-websocket-connection.js
@@ -0,0 +1,86 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see .
+ ******************************************************************************/
+
+import { useEffect, useRef } from "react";
+import { useDispatch } from "react-redux";
+import { connectWebSocket } from "../../../store/websocketSlice";
+
+//this hook is used to establish websocket connection in the dashboard
+const useWebSocketConnection = (activeICS, signals, widgets) => {
+ const dispatch = useDispatch();
+ const hasConnectedRef = useRef(false);
+ const startUpdaterWidgets = new Set(["Slider", "Button", "NumberInput"]);
+
+ useEffect(() => {
+ //we want to connect only once after widgets, signals and activeICs are loaded
+ if (
+ !hasConnectedRef.current &&
+ signals.length > 0 &&
+ activeICS.length > 0 &&
+ widgets.length > 0
+ ) {
+ activeICS.forEach((i) => {
+ if (i.websocketurl) {
+ console.log("CONNECTING TO A WEBSOCKET FROM", i);
+ let inputValues = [];
+ //read values of all the manipulation widgets and set their values to the corresponding input signals
+ const inputSignals = [...signals].filter((s) => s.direction == "in");
+ if (inputSignals.length > 0) {
+ //initialize values for input signals
+ inputValues = new Array(inputSignals.length).fill(0);
+ //go through all the widgets and check if they have a value for any of the input signals
+ //add this value to the inputValues array at the index
+ widgets.forEach((widget) => {
+ if (startUpdaterWidgets.has(widget.type)) {
+ const matchingSignal = inputSignals.find((signal) =>
+ widget.signalIDs.includes(signal.id)
+ );
+ if (
+ matchingSignal &&
+ !isNaN(matchingSignal.index) &&
+ matchingSignal.index < inputValues.length
+ ) {
+ if (widget.type == "Button") {
+ inputValues[matchingSignal.index] = widget.customProperties
+ .pressed
+ ? widget.customProperties.on_value
+ : widget.customProperties.off_value;
+ } else {
+ inputValues[matchingSignal.index] =
+ widget.customProperties.value;
+ }
+ }
+ }
+ });
+ }
+
+ dispatch(
+ connectWebSocket({
+ url: i.websocketurl,
+ id: i.id,
+ inputValues: inputValues,
+ })
+ );
+ }
+ });
+
+ hasConnectedRef.current = true;
+ }
+ }, [activeICS, signals, widgets]);
+};
+
+export default useWebSocketConnection;
diff --git a/src/pages/dashboards/styles.js b/src/pages/dashboards/styles.js
new file mode 100644
index 00000000..98e69c78
--- /dev/null
+++ b/src/pages/dashboards/styles.js
@@ -0,0 +1,21 @@
+export const buttonStyle = {
+ marginLeft: "5px",
+};
+
+export const iconStyle = {
+ height: "25px",
+ width: "25px",
+};
+
+export const tableHeadingStyle = {
+ paddingTop: "30px",
+};
+
+export const dialogWarningLabel = {
+ background: "#eb4d2a",
+ color: "white",
+};
+
+export const signalDialogCheckButton = {
+ float: "right",
+};
diff --git a/src/pages/dashboards/widget/edit-widget/edit-widget.js b/src/pages/dashboards/widget/edit-widget/edit-widget.js
index 5d5a0ea1..c0635821 100644
--- a/src/pages/dashboards/widget/edit-widget/edit-widget.js
+++ b/src/pages/dashboards/widget/edit-widget/edit-widget.js
@@ -148,9 +148,6 @@ class EditWidgetDialog extends React.Component {
} else {
customProperty ? changeObject[parts[0]][parts[1]] = e.target.value : changeObject[e.target.id] = e.target.value ;
}
-
- console.log(changeObject)
-
this.setState({ temporal: changeObject});
}
diff --git a/src/pages/dashboards/widget/websocket-store.js b/src/pages/dashboards/widget/websocket-store.js
deleted file mode 100644
index 0665cf9f..00000000
--- a/src/pages/dashboards/widget/websocket-store.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see .
- ******************************************************************************/
-
-import ArrayStore from '../common/array-store';
-
-class WebsocketStore extends ArrayStore {
-
- updateSocketStatus(state, socket) {
- let checkInclusion = false;
- state.forEach((element) => {
- if (element.url === socket.url) {
- element.connected = socket.connected;
- checkInclusion = true;
- }
- })
- if (!checkInclusion) {
- state.push(socket);
- }
- this.__emitChange();
-
- return state;
- }
-
- reduce(state, action) {
- let tempSocket = {};
- switch (action.type) {
-
- case 'websocket/connected':
- tempSocket.url = action.data;
- tempSocket.connected = true;
- return this.updateSocketStatus(state, tempSocket);
-
- case 'websocket/connection-error':
- tempSocket.url = action.data;
- tempSocket.connected = false;
- return this.updateSocketStatus(state, tempSocket);
-
-
- default:
- return super.reduce(state, action);
- }
- }
-}
-
-export default new WebsocketStore();
diff --git a/src/pages/dashboards/widget/widget-container.js b/src/pages/dashboards/widget/widget-container.js
index ff336e14..61ea65e8 100644
--- a/src/pages/dashboards/widget/widget-container.js
+++ b/src/pages/dashboards/widget/widget-container.js
@@ -15,12 +15,12 @@
* along with VILLASweb. If not, see .
******************************************************************************/
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-import { Rnd } from 'react-rnd';
-import WidgetContextMenu from '../widget/widget-context-menu';
-import {contextMenu} from "react-contexify";
+import React from "react";
+import PropTypes from "prop-types";
+import classNames from "classnames";
+import { Rnd } from "react-rnd";
+import WidgetContextMenu from "../widget/widget-context-menu.jsx";
+import { contextMenu } from "react-contexify";
class WidgetContainer extends React.Component {
constructor(props) {
@@ -36,7 +36,7 @@ class WidgetContainer extends React.Component {
return Math.round(value / this.props.grid) * this.props.grid;
}
- borderWasClicked = event => {
+ borderWasClicked = (event) => {
if (event.button !== 2) {
return;
}
@@ -60,11 +60,19 @@ class WidgetContainer extends React.Component {
const widget = this.props.widget;
// resize depends on direction
- if (direction === 'left' || direction === 'topLeft' || direction === 'bottomLeft') {
+ if (
+ direction === "left" ||
+ direction === "topLeft" ||
+ direction === "bottomLeft"
+ ) {
widget.x -= delta.width;
}
- if (direction === 'top' || direction === 'topLeft' || direction === 'topRight') {
+ if (
+ direction === "top" ||
+ direction === "topLeft" ||
+ direction === "topRight"
+ ) {
widget.y -= delta.height;
}
@@ -87,16 +95,15 @@ class WidgetContainer extends React.Component {
e.preventDefault();
contextMenu.show({
event: e,
- id: 'widgetMenu' + index,
- })
+ id: "widgetMenu" + index,
+ });
}
render() {
-
const widget = this.props.widget;
let contextMenu = (
)
+ />
+ );
- if ( !this.props.editing ){
+ if (!this.props.editing) {
const containerStyle = {
width: Number(widget.width),
height: Number(widget.height),
left: Number(widget.x),
top: Number(widget.y),
zIndex: Number(widget.z),
- position: 'absolute'
+ position: "absolute",
};
- return