1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

updated dashboard page

Signed-off-by: Andrii Podriez <andrey5577990@gmail.com>
This commit is contained in:
Andrii Podriez 2024-07-25 13:52:51 +02:00 committed by al3xa23
parent 8d5076df1f
commit 67a5484ca3
5 changed files with 750 additions and 0 deletions

View file

@ -0,0 +1,112 @@
/**
* 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 <http://www.gnu.org/licenses/>.
******************************************************************************/
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 <IconButton
key={buttonkey++}
childKey={buttonkey}
icon={icon}
disabled={true}
hidetooltip={true}
tooltip={tooltip}
tipPlacement={'bottom'}
onClick={clickFn}
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
} else {
return <IconButton
key={buttonkey++}
childKey={buttonkey}
icon={icon}
tooltip={tooltip}
tipPlacement={'bottom'}
onClick={clickFn}
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
}
}
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 <div className='section-buttons-group-right'>
{buttons}
</div>;
}
}
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;

View file

@ -0,0 +1,447 @@
// import React, { useState, useEffect, useCallback } from 'react';
// import { useParams } from 'react-router-dom';
// import Fullscreenable from 'react-fullscreenable';
// import classNames from 'classnames';
// import 'react-contexify/dist/ReactContexify.min.css';
// import EditWidget from '../../widget/edit-widget/edit-widget';
// import EditFilesDialog from '../../file/edit-files';
// import EditSignalMappingDialog from '../scenarios/dialogs/edit-signal-mapping'
// import WidgetToolbox from '../../widget/widget-toolbox';
// import WidgetArea from './widget-area';
// import DashboardButtonGroup from './dashboard-button-group';
// import IconToggleButton from '../../common/buttons/icon-toggle-button';
// import WidgetContainer from '../../widget/widget-container';
// import Widget from "../../widget/widget";
// import {
// useGetDashboardQuery,
// useLazyGetWidgetsQuery,
// useLazyGetConfigsQuery,
// useAddWidgetMutation,
// useUpdateWidgetMutation,
// useDeleteWidgetMutation
// } from '../../store/apiSlice';
// const startUpdaterWidgets = new Set(['Slider', 'Button', 'NumberInput']);
// const Dashboard = ({ isFullscreen, toggleFullscreen }) => {
// const params = useParams();
// const { data: dashboardRes, error: dashboardError, isLoading: isDashboardLoading } = useGetDashboardQuery(params.dashboard);
// const dashboard = dashboardRes ? dashboardRes.dashboard : {};
// const [triggerGetWidgets] = useLazyGetWidgetsQuery();
// const [triggerGetConfigs] = useLazyGetConfigsQuery();
// const [addWidget] = useAddWidgetMutation();
// const [updateWidget] = useUpdateWidgetMutation();
// const [deleteWidgetMutation] = useDeleteWidgetMutation();
// const [widgets, setWidgets] = useState([]);
// const [configs, setConfigs] = useState([]);
// const [signals, setSignals] = useState([]);
// const [sessionToken, setSessionToken] = useState(localStorage.getItem("token"));
// const [files, setFiles] = useState([]);
// const [ics, setIcs] = useState([]);
// const [editing, setEditing] = useState(false);
// const [paused, setPaused] = useState(false);
// const [editModal, setEditModal] = useState(false);
// const [editOutputSignalsModal, setEditOutputSignalsModal] = useState(false);
// const [editInputSignalsModal, setEditInputSignalsModal] = useState(false);
// const [filesEditModal, setFilesEditModal] = useState(false);
// const [filesEditSaveState, setFilesEditSaveState] = useState([]);
// const [modalData, setModalData] = useState(null);
// const [modalIndex, setModalIndex] = useState(null);
// const [widgetChangeData, setWidgetChangeData] = useState([]);
// const [widgetOrigIDs, setWidgetOrigIDs] = useState([]);
// const [maxWidgetHeight, setMaxWidgetHeight] = useState(null);
// const [locked, setLocked] = useState(false);
// useEffect(() => {
// if (dashboard.id) {
// fetchWidgets(dashboard.id);
// fetchConfigs(dashboard.scenarioID);
// }
// }, [dashboard]);
// const fetchWidgets = async (dashboardID) => {
// try {
// const res = await triggerGetWidgets(dashboardID).unwrap();
// if (res.widgets) {
// setWidgets(res.widgets);
// }
// } catch (err) {
// console.log('error', err);
// }
// };
// const fetchConfigs = async (scenarioID) => {
// try {
// const res = await triggerGetConfigs(scenarioID).unwrap();
// if (res.configs) {
// setConfigs(res.configs);
// }
// } catch (err) {
// console.log('error', err);
// }
// };
// const handleKeydown = useCallback((e) => {
// switch (e.key) {
// case ' ':
// case 'p':
// setPaused(prevPaused => !prevPaused);
// break;
// case 'e':
// setEditing(prevEditing => !prevEditing);
// break;
// case 'f':
// toggleFullscreen();
// break;
// default:
// }
// }, [toggleFullscreen]);
// useEffect(() => {
// window.addEventListener('keydown', handleKeydown);
// return () => {
// window.removeEventListener('keydown', handleKeydown);
// };
// }, [handleKeydown]);
// const handleDrop = async (widget) => {
// widget.dashboardID = dashboard.id;
// if (widget.type === 'ICstatus') {
// let allICids = ics.map(ic => ic.id);
// widget.customProperties.checkedIDs = allICids;
// }
// try {
// const res = await addWidget(widget).unwrap();
// if (res) {
// fetchWidgets(dashboard.id);
// }
// } catch (err) {
// console.log('error', err);
// }
// };
// const widgetChange = async (widget) => {
// setWidgetChangeData(prevWidgetChangeData => [...prevWidgetChangeData, widget]);
// try {
// await updateWidget({ widgetID: widget.id, updatedWidget: { widget } }).unwrap();
// fetchWidgets(dashboard.id);
// } catch (err) {
// console.log('error', err);
// }
// };
// const onChange = async (widget) => {
// try {
// await updateWidget({ widgetID: widget.id, updatedWidget: { widget } }).unwrap();
// fetchWidgets(dashboard.id);
// } catch (err) {
// console.log('error', err);
// }
// };
// const onSimulationStarted = () => {
// widgets.forEach(async (widget) => {
// if (startUpdaterWidgets.has(widget.type)) {
// widget.customProperties.simStartedSendValue = true;
// try {
// await updateWidget({ widgetID: widget.id, updatedWidget: { widget } }).unwrap();
// } catch (err) {
// console.log('error', err);
// }
// }
// });
// };
// const editWidget = (widget, index) => {
// setEditModal(true);
// setModalData(widget);
// setModalIndex(index);
// };
// const duplicateWidget = async (widget) => {
// let widgetCopy = { ...widget, id: undefined, x: widget.x + 50, y: widget.y + 50 };
// try {
// const res = await addWidget({ widget: widgetCopy }).unwrap();
// if (res) {
// fetchWidgets(dashboard.id);
// }
// } catch (err) {
// console.log('error', err);
// }
// };
// const startEditFiles = () => {
// let tempFiles = files.map(file => ({ id: file.id, name: file.name }));
// setFilesEditModal(true);
// setFilesEditSaveState(tempFiles);
// };
// const closeEditFiles = () => {
// widgets.forEach(widget => {
// if (widget.type === "Image") {
// widget.customProperties.update = true;
// }
// });
// setFilesEditModal(false);
// };
// const closeEdit = async (data) => {
// if (!data) {
// setEditModal(false);
// setModalData(null);
// setModalIndex(null);
// return;
// }
// if (data.type === "Image") {
// data.customProperties.update = true;
// }
// try {
// await updateWidget({ widgetID: data.id, updatedWidget: { widget: data } }).unwrap();
// fetchWidgets(dashboard.id);
// } catch (err) {
// console.log('error', err);
// }
// setEditModal(false);
// setModalData(null);
// setModalIndex(null);
// };
// const deleteWidget = async (widgetID) => {
// try {
// await deleteWidgetMutation(widgetID).unwrap();
// fetchWidgets(dashboard.id);
// } catch (err) {
// console.log('error', err);
// }
// };
// const startEditing = () => {
// let originalIDs = widgets.map(widget => widget.id);
// widgets.forEach(async (widget) => {
// if (widget.type === 'Slider' || widget.type === 'NumberInput' || widget.type === 'Button') {
// try {
// await updateWidget({ widgetID: widget.id, updatedWidget: { widget } }).unwrap();
// } catch (err) {
// console.log('error', err);
// }
// } else if (widget.type === 'Image') {
// widget.customProperties.update = true;
// }
// });
// setEditing(true);
// setWidgetOrigIDs(originalIDs);
// };
// const saveEditing = () => {
// widgets.forEach(async (widget) => {
// if (widget.type === 'Image') {
// widget.customProperties.update = true;
// }
// try {
// await updateWidget({ widgetID: widget.id, updatedWidget: { widget } }).unwrap();
// } catch (err) {
// console.log('error', err);
// }
// });
// setEditing(false);
// setWidgetChangeData([]);
// };
// const cancelEditing = () => {
// widgets.forEach(async (widget) => {
// if (widget.type === 'Image') {
// widget.customProperties.update = true;
// }
// if (!widgetOrigIDs.includes(widget.id)) {
// try {
// await deleteWidget(widget.id).unwrap();
// } catch (err) {
// console.log('error', err);
// }
// }
// });
// setEditing(false);
// setWidgetChangeData([]);
// };
// const setGrid = (value) => {
// setState(prevState => ({ ...prevState, dashboard: { ...dashboard, grid: value } }));
// };
// const setDashboardSize = (value) => {
// const maxHeight = Object.values(widgets).reduce((currentHeight, widget) => {
// const absolutHeight = widget.y + widget.height;
// return absolutHeight > currentHeight ? absolutHeight : currentHeight;
// }, 0);
// if (value === -1) {
// if (dashboard.height >= 450 && dashboard.height >= (maxHeight + 80)) {
// setState(prevState => ({ ...prevState, dashboard: { ...dashboard, height: dashboard.height - 50 } }));
// }
// } else {
// setState(prevState => ({ ...prevState, dashboard: { ...dashboard, height: dashboard.height + 50 } }));
// }
// };
// const pauseData = () => setPaused(true);
// const unpauseData = () => setPaused(false);
// const editInputSignals = () => setEditInputSignalsModal(true);
// const editOutputSignals = () => setEditOutputSignalsModal(true);
// const closeEditSignalsModal = (direction) => {
// if (direction === "in") {
// setEditInputSignalsModal(false);
// } else if (direction === "out") {
// setEditOutputSignalsModal(false);
// }
// };
// const buttonStyle = { marginLeft: '10px' };
// const iconStyle = { height: '25px', width: '25px' };
// const grid = dashboard.grid;
// const boxClasses = classNames('section', 'box', { 'fullscreen-padding': isFullscreen });
// let dropZoneHeight = dashboard.height;
// if (isDashboardLoading) {
// return <div>Loading...</div>;
// }
// if (dashboardError) {
// return <div>Error. Dashboard not found</div>;
// }
// return (
// <div className={boxClasses}>
// <div key={"header-box"} className='section-header box-header'>
// <div key={"title"} className="section-title">
// <h2>
// {dashboard.name}
// <span key={"toggle-lock-button"} className='icon-button'>
// <IconToggleButton
// childKey={0}
// checked={locked}
// index={dashboard.id}
// checkedIcon='lock'
// uncheckedIcon='lock-open'
// tooltipChecked='Dashboard is locked, cannot be edited'
// tooltipUnchecked='Dashboard is unlocked, can be edited'
// disabled={true}
// buttonStyle={buttonStyle}
// iconStyle={iconStyle}
// />
// </span>
// </h2>
// </div>
// <DashboardButtonGroup
// key={"dashboard-buttons"}
// locked={locked}
// editing={editing}
// onEdit={startEditing}
// fullscreen={isFullscreen}
// paused={paused}
// onSave={saveEditing}
// onCancel={cancelEditing}
// onFullscreen={toggleFullscreen}
// onPause={pauseData}
// onUnpause={unpauseData}
// onEditFiles={startEditFiles}
// onEditOutputSignals={editOutputSignals}
// onEditInputSignals={editInputSignals}
// />
// </div>
// <div key={"dashboard-area"} className="box box-content" onContextMenu={(e) => e.preventDefault()}>
// {editing &&
// <WidgetToolbox
// key={"widget-toolbox"}
// grid={grid}
// onGridChange={setGrid}
// dashboard={dashboard}
// onDashboardSizeChange={setDashboardSize}
// widgets={widgets}
// />
// }
// <WidgetArea
// key={"widget-area"}
// widgets={widgets}
// editing={editing}
// dropZoneHeight={dropZoneHeight}
// grid={grid}
// onWidgetAdded={handleDrop}
// >
// {widgets != null && Object.keys(widgets).map(widgetKey => (
// <div key={"widget-container-wrapper" + widgetKey}>
// <WidgetContainer
// widget={widgets[widgetKey]}
// key={"widget-container" + widgetKey}
// index={parseInt(widgetKey, 10)}
// grid={grid}
// onWidgetChange={widgetChange}
// editing={editing}
// paused={paused}
// onEdit={editWidget}
// onDuplicate={duplicateWidget}
// onDelete={(widget, index) => deleteWidget(widget.id)}
// onChange={editing ? widgetChange : onChange}
// >
// <Widget
// key={"widget" + widgetKey}
// data={widgets[widgetKey]}
// editing={editing}
// index={parseInt(widgetKey, 10)}
// paused={paused}
// onSimulationStarted={onSimulationStarted}
// ics={ics}
// configs={configs}
// scenarioID={dashboard.scenarioID}
// />
// </WidgetContainer>
// </div>
// ))}
// </WidgetArea>
// <EditWidget
// key={"edit-widget"}
// sessionToken={sessionToken}
// show={editModal}
// onClose={closeEdit}
// widget={modalData}
// signals={signals}
// files={files}
// ics={ics}
// configs={configs}
// scenarioID={dashboard.scenarioID}
// />
// <EditSignalMappingDialog
// key={"edit-signal-mapping-input-dialog"}
// show={editInputSignalsModal}
// onCloseEdit={closeEditSignalsModal}
// direction="Input"
// signals={signals}
// configID={null}
// configs={configs}
// sessionToken={sessionToken}
// />
// </div>
// </div>
// );
// };
// export default Fullscreenable()(Dashboard);
const Dashboard = (props) => {
return <div></div>
}
export default Dashboard;

View file

@ -0,0 +1,77 @@
/**
* 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 <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { DropTarget } from 'react-dnd';
import classNames from 'classnames';
const dropzoneTarget = {
drop(props, monitor, component) {
// get drop position
var position = monitor.getSourceClientOffset();
var dropzoneRect = component.wrapper.getBoundingClientRect();
position.x -= dropzoneRect.left;
position.y -= dropzoneRect.top;
// Z-Index is one more the top most children
let foundZ = props.widgets.reduce( (maxZ, currentWidget) => {
if (currentWidget != null) {
// Is there a simpler way? Is not easy to expose a getter in a Container.create(Component)
if (currentWidget.z > maxZ) {
return currentWidget.z;
}
}
return maxZ;
}, 0)
position.z = foundZ >= 100? foundZ : foundZ += 10;
if(monitor.getItem().name === "Box"){
position.z = 0;
}
props.onDrop(monitor.getItem(), position);
}
};
function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
};
}
class Dropzone extends React.Component {
render() {
var toolboxClass = classNames({
'box-content': true,
'toolbox-dropzone': true,
'toolbox-dropzone-active': this.props.isOver && this.props.canDrop && this.props.editing,
'toolbox-dropzone-editing': this.props.editing
});
return this.props.connectDropTarget(
<div className={toolboxClass} style={{ height: this.props.height}} ref={wrapper => this.wrapper = wrapper}>
{this.props.children}
</div>
);
}
}
export default DropTarget('widget', dropzoneTarget, collect)(Dropzone);

View file

@ -0,0 +1,38 @@
/**
* 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 <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
class Grid extends React.Component {
render() {
if (this.props.disabled) return false;
return (
<svg width="100%" height="100%">
<defs>
<pattern id="grid" width={this.props.size} height={this.props.size} patternUnits="userSpaceOnUse">
<path d={"M " + this.props.size + " 0 L 0 0 0 " + this.props.size} fill="none" stroke="gray" strokeWidth="0.5" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
);
}
}
export default Grid;

View file

@ -0,0 +1,76 @@
/**
* 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 <http://www.gnu.org/licenses/>.
******************************************************************************/
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) {
if (this.props.grid === 1) {
return value;
}
return Math.round(value / this.props.grid) * this.props.grid;
}
handleDrop = (item, position) => {
position.x = this.snapToGrid(position.x);
position.y = this.snapToGrid(position.y);
const widget = WidgetFactory.createWidgetOfType(item.name, position);
if (this.props.onWidgetAdded != null) {
this.props.onWidgetAdded(widget);
}
}
render() {
return <Dropzone
height={this.props.dropZoneHeight}
onDrop={this.handleDrop}
editing={this.props.editing}
widgets={this.props.widgets}
>
{this.props.children}
<Grid
size={this.props.grid}
disabled={this.props.grid === 1 || this.props.editing !== true}
/>
</Dropzone>;
}
}
WidgetArea.propTypes = {
children: PropTypes.node, //TODO is .node correct here? Was .children before leading to compile error
editing: PropTypes.bool,
grid: PropTypes.number,
//widgets: PropTypes.array,
onWidgetAdded: PropTypes.func
};
WidgetArea.defaultProps = {
widgets: {}
};
export default WidgetArea;