From 8dea2f66332d96c530f35de1a2d283a0b30df24b Mon Sep 17 00:00:00 2001 From: foxriver76 Date: Mon, 20 Nov 2023 08:49:05 +0100 Subject: [PATCH] started working with redux --- package.json | 1 + src/.eslintrc.js | 1 + src/package.json | 4 +- src/src/App.jsx | 108 +++++++++++----------- src/src/Attributes/Widget/index.jsx | 2 +- src/src/Marketplace/MarketplaceDialog.jsx | 12 ++- src/src/Runtime.jsx | 27 +++--- src/src/Store.tsx | 62 +++++++++++++ src/src/Toolbar/ToolbarItems.jsx | 7 +- src/src/Toolbar/Views.jsx | 3 +- src/src/Toolbar/ViewsManager/index.jsx | 20 ++-- src/src/Toolbar/Widgets.jsx | 20 ++-- src/src/Vis/visUtils.jsx | 11 ++- src/tsconfig.json | 32 +++++++ tsconfig.json | 34 +++++++ 15 files changed, 247 insertions(+), 97 deletions(-) create mode 100644 src/src/Store.tsx create mode 100644 src/tsconfig.json create mode 100644 tsconfig.json diff --git a/package.json b/package.json index cef471a26..e973d4150 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@alcalzone/release-script-plugin-iobroker": "^3.6.0", "@alcalzone/release-script-plugin-license": "^3.5.9", "@iobroker/vis-2-widgets-testing": "^0.3.0", + "@tsconfig/node16": "^16.1.1", "chai": "^4.3.10", "gulp": "^4.0.2", "iobroker.web": "*", diff --git a/src/.eslintrc.js b/src/.eslintrc.js index c62f0828e..2558156af 100644 --- a/src/.eslintrc.js +++ b/src/.eslintrc.js @@ -9,6 +9,7 @@ module.exports = { 'airbnb', // 'react-app', 'plugin:eqeqeq-fix/recommended', + 'plugin:@typescript-eslint/recommended', ], parserOptions: { ecmaFeatures: { diff --git a/src/package.json b/src/package.json index 4303ac340..a04d08813 100644 --- a/src/package.json +++ b/src/package.json @@ -16,6 +16,7 @@ "@mui/material": "5.14.14", "@mui/styles": "5.14.14", "@mui/x-date-pickers": "^6.18.1", + "@reduxjs/toolkit": "^1.9.7", "@sentry/browser": "^7.80.1", "@sentry/integrations": "^7.80.1", "ace-builds": "^1.31.2", @@ -47,6 +48,7 @@ "react-dropzone": "^14.2.3", "react-icons": "^4.12.0", "react-scripts": "^5.0.1", + "redux": "^4.2.1", "sass": "^1.69.5", "uuid": "^9.0.1" }, @@ -68,4 +70,4 @@ "not ie <= 11", "not op_mini all" ] -} \ No newline at end of file +} diff --git a/src/src/App.jsx b/src/src/App.jsx index 257d66653..ee60b04a0 100644 --- a/src/src/App.jsx +++ b/src/src/App.jsx @@ -32,6 +32,7 @@ import { Message as MessageDialog, SelectFile as SelectFileDialog, Icon, } from '@iobroker/adapter-react-v5'; +import { store, updateProject } from './Store'; import Attributes from './Attributes'; import Palette from './Palette'; @@ -259,6 +260,7 @@ class App extends Runtime { async componentDidMount() { super.componentDidMount(); + store.subscribe(() => this.render); window.addEventListener('keydown', this.onKeyDown, false); window.addEventListener('beforeunload', this.onBeforeUnload, false); } @@ -312,11 +314,11 @@ class App extends Runtime { if (controlKey && e.key === 'a') { e.preventDefault(); if (this.state.selectedGroup) { - this.setSelectedWidgets(Object.keys(this.state.visProject[this.state.selectedView].widgets) - .filter(widget => !this.state.visProject[this.state.selectedView].widgets[widget].data.locked && this.state.visProject[this.state.selectedView].widgets[widget].groupid === this.state.selectedGroup)); + this.setSelectedWidgets(Object.keys(store.getState().visProject[this.state.selectedView].widgets) + .filter(widget => !store.getState().visProject[this.state.selectedView].widgets[widget].data.locked && store.getState().visProject[this.state.selectedView].widgets[widget].groupid === this.state.selectedGroup)); } else { - this.setSelectedWidgets(Object.keys(this.state.visProject[this.state.selectedView].widgets) - .filter(widget => !this.state.visProject[this.state.selectedView].widgets[widget].data.locked && !this.state.visProject[this.state.selectedView].widgets[widget].grouped)); + this.setSelectedWidgets(Object.keys(store.getState().visProject[this.state.selectedView].widgets) + .filter(widget => !store.getState().visProject[this.state.selectedView].widgets[widget].data.locked && !store.getState().visProject[this.state.selectedView].widgets[widget].grouped)); } } if (e.key === 'Escape') { @@ -358,7 +360,7 @@ class App extends Runtime { // Check that all selectedWidgets exist for (let i = selectedWidgets.length - 1; i >= 0; i--) { - if (!this.state.visProject[selectedView] || !this.state.visProject[selectedView].widgets || !this.state.visProject[selectedView].widgets[selectedWidgets[i]]) { + if (!store.getState().visProject[selectedView] || !store.getState().visProject[selectedView].widgets || !store.getState().visProject[selectedView].widgets[selectedWidgets[i]]) { selectedWidgets.splice(i, 1); } } @@ -376,7 +378,7 @@ class App extends Runtime { */ getNewWidgetIdNumber = (isGroup, project, offset = 0) => { const widgets = []; - project = project || this.state.visProject; + project = project || store.getState().visProject; Object.keys(project).forEach(view => project[view].widgets && Object.keys(project[view].widgets).forEach(widget => widgets.push(widget))); @@ -421,7 +423,7 @@ class App extends Runtime { }; addWidget = async (widgetType, x, y, data, style) => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; const newKey = this.getNewWidgetId(); widgets[newKey] = { @@ -495,7 +497,7 @@ class App extends Runtime { updateWidgetsAction = async (marketplace, widgets) => { await this.installWidget(marketplace.widget_id, marketplace.id); - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); widgets.forEach(view => { view.widgets.forEach(widget => { const widgetData = project[view.name].widgets[widget]; @@ -522,7 +524,7 @@ class App extends Runtime { }; deleteWidgetsAction = async () => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; this.state.selectedWidgets.forEach(selectedWidget => { if (widgets[selectedWidget].tpl === '_tplGroup') { @@ -550,7 +552,7 @@ class App extends Runtime { }; lockWidgets = async type => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; this.state.selectedWidgets.forEach(selectedWidget => widgets[selectedWidget].data.locked = type === 'lock'); @@ -580,10 +582,10 @@ class App extends Runtime { cutCopyWidgets = async type => { const widgets = {}; - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); this.state.selectedWidgets.forEach(selectedWidget => { - widgets[selectedWidget] = this.state.visProject[this.state.selectedView].widgets[selectedWidget]; + widgets[selectedWidget] = store.getState().visProject[this.state.selectedView].widgets[selectedWidget]; if (type === 'cut' && project[this.state.selectedView]) { delete project[this.state.selectedView].widgets[selectedWidget]; } @@ -632,7 +634,7 @@ class App extends Runtime { }; pasteWidgets = async () => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; const newKeys = []; @@ -651,10 +653,10 @@ class App extends Runtime { let newKey; if (newWidget.tpl === '_tplGroup') { - newKey = this.getNewGroupId(this.state.visProject, groupOffset); + newKey = this.getNewGroupId(store.getState().visProject, groupOffset); groupOffset++; } else { - newKey = this.getNewWidgetId(this.state.visProject, widgetOffset); + newKey = this.getNewWidgetId(store.getState().visProject, widgetOffset); widgetOffset++; } @@ -668,7 +670,7 @@ class App extends Runtime { }; cloneWidgets = async () => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; const newKeys = []; @@ -689,7 +691,7 @@ class App extends Runtime { }; alignWidgets = type => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; const newCoordinates = { left: 0, top: 0, width: 0, height: 0, right: 0, bottom: 0, @@ -850,7 +852,7 @@ class App extends Runtime { }; orderWidgets = type => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; let minZ = 0; let maxZ = 0; @@ -902,7 +904,7 @@ class App extends Runtime { } groupWidgets = () => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; const group = { tpl: '_tplGroup', @@ -951,7 +953,7 @@ class App extends Runtime { }; ungroupWidgets = () => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgets = project[this.state.selectedView].widgets; const group = widgets[this.state.selectedWidgets[0]]; group.data.members.forEach(member => { @@ -970,7 +972,7 @@ class App extends Runtime { }; setSelectedGroup = groupId => { - if (this.state.visProject[this.state.selectedView].widgets[groupId].marketplace) { + if (store.getState().visProject[this.state.selectedView].widgets[groupId].marketplace) { return; } this.setState({ selectedGroup: groupId }); @@ -1028,7 +1030,9 @@ class App extends Runtime { this.saveHistory(project); } - await this.setStateAsync({ visProject: project, needSave: true }); + store.dispatch(updateProject(project)); + console.log('dispatched'); + await this.setStateAsync({ needSave: true }); // save changes after 1 second this.savingTimer && clearTimeout(this.savingTimer); @@ -1056,7 +1060,7 @@ class App extends Runtime { }; unsyncMultipleWidgets(project) { - project = project || this.state.visProject; + project = project || store.getState().visProject; Object.keys(project).forEach(view => { if (view === '___settings') { return; @@ -1101,7 +1105,7 @@ class App extends Runtime { toggleView = async (view, isShow, isActivate) => { let changed = false; - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const pos = project.___settings.openedViews.indexOf(view); if (isShow && pos === -1) { project.___settings.openedViews.push(view); @@ -1158,7 +1162,7 @@ class App extends Runtime { }; onWidgetsChanged = (changedData, view, viewSettings) => { - this.tempProject = this.tempProject || JSON.parse(JSON.stringify(this.state.visProject)); + this.tempProject = this.tempProject || JSON.parse(JSON.stringify(store.getState().visProject)); changedData && changedData.forEach(item => { if (item.style) { const currentStyle = this.tempProject[item.view].widgets[item.wid].style; @@ -1287,7 +1291,7 @@ class App extends Runtime { installWidget = async (widgetId, id) => { if (window.VisMarketplace?.api) { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const marketplaceWidget = await window.VisMarketplace.api.apiGetWidgetRevision(widgetId, id); if (!project.___settings.marketplace) { project.___settings.marketplace = []; @@ -1303,7 +1307,7 @@ class App extends Runtime { }; uninstallWidget = async widget => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widgetIndex = project.___settings.marketplace.findIndex(item => item.id === widget); if (widgetIndex !== -1) { project.___settings.marketplace.splice(widgetIndex, 1); @@ -1318,7 +1322,7 @@ class App extends Runtime { widgets.forEach(_widget => { if (_widget.isRoot) { - _widget.marketplace = JSON.parse(JSON.stringify(this.state.visProject.___settings.marketplace.find(item => item.id === id))); + _widget.marketplace = JSON.parse(JSON.stringify(store.getState().visProject.___settings.marketplace.find(item => item.id === id))); } if (_widget.tpl === '_tplGroup') { let newKey = `g${newGroupKeyNumber.toString().padStart(6, '0')}`; @@ -1365,17 +1369,17 @@ class App extends Runtime { }; addMarketplaceWidget = async (id, x, y, widgetId, oldData, oldStyle) => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); - const widgets = JSON.parse(JSON.stringify(this.state.visProject.___settings.marketplace.find(item => item.id === id).widget)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); + const widgets = JSON.parse(JSON.stringify(store.getState().visProject.___settings.marketplace.find(item => item.id === id).widget)); this.importMarketplaceWidget(project, this.state.selectedView, widgets, id, x, y, widgetId, oldData, oldStyle); await this.changeProject(project); }; updateWidget = async id => { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); const widget = project[this.state.selectedView].widgets[id]; if (widget && widget.marketplace) { - const marketplace = JSON.parse(JSON.stringify(this.state.visProject.___settings.marketplace.find(item => item.widget_id === widget.marketplace.widget_id))); + const marketplace = JSON.parse(JSON.stringify(store.getState().visProject.___settings.marketplace.find(item => item.widget_id === widget.marketplace.widget_id))); await this.deleteWidgetsAction(); await this.addMarketplaceWidget(marketplace.id, null, null, id, widget.data, widget.style); } @@ -1425,8 +1429,8 @@ class App extends Runtime { askAboutInclude = (wid, toWid, cb) => this.setState({ askAboutInclude: { wid, toWid, cb } }); renderTabs() { - const views = Object.keys(this.state.visProject) - .filter(view => !view.startsWith('__') && this.state.visProject.___settings.openedViews.includes(view)); + const views = Object.keys(store.getState().visProject) + .filter(view => !view.startsWith('__') && store.getState().visProject.___settings.openedViews.includes(view)); return
{this.state.hidePalette ? @@ -1471,7 +1475,7 @@ class App extends Runtime { { views.map(view => { const isGroupEdited = !!this.state.selectedGroup && view === this.state.selectedView; - const viewSettings = isGroupEdited ? {} : (this.state.visProject[view].settings || {}); + const viewSettings = isGroupEdited ? {} : (store.getState().visProject[view].settings || {}); let icon = viewSettings.navigationIcon || viewSettings.navigationImage; if (icon && icon.startsWith('_PRJ_NAME/')) { icon = `../${this.adapterName}.${this.instance}/${this.state.projectName}${icon.substring(9)}`; // "_PRJ_NAME".length = 9 @@ -1526,7 +1530,7 @@ class App extends Runtime { { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + const project = JSON.parse(JSON.stringify(store.getState().visProject)); project.___settings.openedViews = [this.state.selectedView]; this.changeProject(project, true); }} @@ -1572,7 +1576,7 @@ class App extends Runtime { setMarketplaceDialog={this.setMarketplaceDialog} updateWidgets={this.updateWidgets} selectedView={this.state.selectedView} - project={this.state.visProject} + project={store.getState().visProject} changeProject={this.changeProject} socket={this.socket} editMode={this.state.editMode} @@ -1587,7 +1591,7 @@ class App extends Runtime { return
{this.renderTabs()}
{this.state.showCode ?
-                        {JSON.stringify(this.state.visProject, null, 2)}
+                        {JSON.stringify(store.getState().visProject, null, 2)}
                     
: null}
{ + Object.keys(store.getState().visProject).forEach(view => { if (view !== '___settings') { const viewWidgets = { name: view, widgets: [], }; - Object.keys(this.state.visProject[view].widgets).forEach(widget => { - if (this.state.visProject[view].widgets[widget].marketplace?.widget_id === this.state.updateWidgetsDialog.widget_id && - this.state.visProject[view].widgets[widget].marketplace?.version !== this.state.updateWidgetsDialog.version) { + Object.keys(store.getState().visProject[view].widgets).forEach(widget => { + if (store.getState().visProject[view].widgets[widget].marketplace?.widget_id === this.state.updateWidgetsDialog.widget_id && + store.getState().visProject[view].widgets[widget].marketplace?.version !== this.state.updateWidgetsDialog.version) { viewWidgets.widgets.push(widget); } }); @@ -1899,8 +1903,6 @@ class App extends Runtime { ; } - // console.log(this.state.visProject); - if (this.state.showProjectsDialog) { return @@ -1911,7 +1913,7 @@ class App extends Runtime { ; } - if (!this.state.loaded || !this.state.visProject || !this.state.userGroups) { + if (!this.state.loaded || !store.getState().visProject.___settings || !this.state.userGroups) { return @@ -1926,7 +1928,7 @@ class App extends Runtime { } for (const i in this.state.selectedWidgets) { - if (!this.state.visProject[this.state.selectedView]?.widgets[this.state.selectedWidgets[i]]) { + if (!store.getState().visProject[this.state.selectedView]?.widgets[this.state.selectedWidgets[i]]) { this.setSelectedWidgets([]); return null; } @@ -1961,10 +1963,9 @@ class App extends Runtime { this.setState({ marketplaceDialog: false })} - project={this.state.visProject} installWidget={this.installWidget} updateWidgets={this.updateWidgets} - installedWidgets={this.state.visProject?.___settings.marketplace} + installedWidgets={store.getState().visProject?.___settings.marketplace} {...this.state.marketplaceDialog} themeName={this.state.themeName} /> : null} diff --git a/src/src/Attributes/Widget/index.jsx b/src/src/Attributes/Widget/index.jsx index 3680fdd60..2f297510c 100644 --- a/src/src/Attributes/Widget/index.jsx +++ b/src/src/Attributes/Widget/index.jsx @@ -551,7 +551,7 @@ class Widget extends Component { static buildDataImage(project, selectedView, selectedWidgets) { const result = {}; - (selectedWidgets || []).sort().forEach(wid => result[wid] = project[selectedView].widgets[wid]); + ([...selectedWidgets] || []).sort().forEach(wid => result[wid] = project[selectedView].widgets[wid]); return JSON.stringify(result); } diff --git a/src/src/Marketplace/MarketplaceDialog.jsx b/src/src/Marketplace/MarketplaceDialog.jsx index f460f9514..bd72e6b83 100644 --- a/src/src/Marketplace/MarketplaceDialog.jsx +++ b/src/src/Marketplace/MarketplaceDialog.jsx @@ -7,6 +7,7 @@ import { import { Close } from '@mui/icons-material'; import { I18n } from '@iobroker/adapter-react-v5'; +import { store } from '../Store'; const MarketplaceDialog = props => { const VisMarketplace = window.VisMarketplace?.default; @@ -16,15 +17,17 @@ const MarketplaceDialog = props => { if (props.installWidget) { installWidget = async marketplace => { const widgets = []; - Object.keys(props.project).forEach(view => { + const project = store.getState().visProject; + + Object.keys(project).forEach(view => { if (view !== '___settings') { const viewWidgets = { name: view, widgets: [], }; - Object.keys(props.project[view].widgets).forEach(widget => { - if (props.project[view].widgets[widget].marketplace?.widget_id === marketplace.widget_id && - props.project[view].widgets[widget].marketplace?.version !== marketplace.version) { + Object.keys(project[view].widgets).forEach(widget => { + if (project[view].widgets[widget].marketplace?.widget_id === marketplace.widget_id && + project[view].widgets[widget].marketplace?.version !== marketplace.version) { viewWidgets.widgets.push(widget); } }); @@ -81,7 +84,6 @@ MarketplaceDialog.propTypes = { installedWidgets: PropTypes.array, updateWidgets: PropTypes.func, installWidget: PropTypes.func, - project: PropTypes.object, themeName: PropTypes.string, }; diff --git a/src/src/Runtime.jsx b/src/src/Runtime.jsx index 2d79b4811..b35ccb6f5 100644 --- a/src/src/Runtime.jsx +++ b/src/src/Runtime.jsx @@ -35,6 +35,8 @@ import { } from './Vis/visUtils'; import VisWidgetsCatalog from './Vis/visWidgetsCatalog'; +import { store, updateProject } from './Store'; + const generateClassName = createGenerateClassName({ productionPrefix: 'vis-r', }); @@ -185,11 +187,11 @@ class Runtime extends GenericApp { .then(file => { try { const ts = JSON.parse(file.file || file).___settings.ts; - if (ts === this.state.visProject.___settings.ts) { + if (ts === store.getState().visProject.___settings.ts) { return; } const tsInt = parseInt(ts.split('.'), 10); - if (tsInt < parseInt(this.state.visProject.___settings.ts.split('.'), 10)) { + if (tsInt < parseInt(store.getState().visProject.___settings.ts.split('.'), 10)) { // ignore older files return; } @@ -327,7 +329,7 @@ class Runtime extends GenericApp { } syncMultipleWidgets(project) { - project = project || this.state.visProject; + project = project || store.getState().visProject; Object.keys(project).forEach(view => { if (view === '___settings') { return; @@ -548,10 +550,11 @@ class Runtime extends GenericApp { visUserCss: null, history: [project], historyCursor: 0, - visProject: project, projectName, }); + store.dispatch(updateProject(project)); + await this.changeView(selectedView); // only in edit mode and only after VisMarketplace was loaded @@ -565,7 +568,7 @@ class Runtime extends GenericApp { this.resolutionTimer && clearTimeout(this.resolutionTimer); this.resolutionTimer = setTimeout(async () => { this.resolutionTimer = null; - const view = Runtime.findViewWithNearestResolution(this.state.visProject); + const view = Runtime.findViewWithNearestResolution(store.getState().visProject); if (view && view !== this.state.selectedView) { await this.changeView(view); } @@ -625,7 +628,7 @@ class Runtime extends GenericApp { async onConnectionReady() { // preload all widgets first if (this.state.widgetsLoaded === Runtime.WIDGETS_LOADING_STEP_HTML_LOADED) { - await VisWidgetsCatalog.collectRxInformation(this.socket, this.state.visProject, this.changeProject); + await VisWidgetsCatalog.collectRxInformation(this.socket, store.getState().visProject, this.changeProject); await this.setStateAsync({ widgetsLoaded: Runtime.WIDGETS_LOADING_STEP_ALL_LOADED }); } @@ -708,7 +711,7 @@ class Runtime extends GenericApp { // Check that all selectedWidgets exist for (let i = selectedWidgets.length - 1; i >= 0; i--) { - if (!this.state.visProject[selectedView] || !this.state.visProject[selectedView].widgets || !this.state.visProject[selectedView].widgets[selectedWidgets[i]]) { + if (!store.getState().visProject[selectedView] || !store.getState().visProject[selectedView].widgets || !store.getState().visProject[selectedView].widgets[selectedWidgets[i]]) { selectedWidgets = selectedWidgets.splice(i, 1); } } @@ -725,8 +728,8 @@ class Runtime extends GenericApp { newState.alignValues = []; } - if (!this.state.runtime && !this.state.visProject.___settings.openedViews.includes(selectedView)) { - const project = JSON.parse(JSON.stringify(this.state.visProject)); + if (!this.state.runtime && !store.getState().visProject.___settings.openedViews.includes(selectedView)) { + const project = JSON.parse(JSON.stringify(store.getState().visProject)); project.___settings.openedViews.push(selectedView); await this.changeProject(project, true); } @@ -795,7 +798,7 @@ class Runtime extends GenericApp { async onWidgetsLoaded() { let widgetsLoaded = Runtime.WIDGETS_LOADING_STEP_HTML_LOADED; if (this.socket.isConnected()) { - await VisWidgetsCatalog.collectRxInformation(this.socket, this.state.visProject, this.changeProject); + await VisWidgetsCatalog.collectRxInformation(this.socket, store.getState().visProject, this.changeProject); widgetsLoaded = Runtime.WIDGETS_LOADING_STEP_ALL_LOADED; } this.setState({ widgetsLoaded }); @@ -975,7 +978,7 @@ class Runtime extends GenericApp { visCommonCss={this.state.visCommonCss} visUserCss={this.state.visUserCss} lang={this.socket.systemLang} - views={this.state.visProject} + views={store.getState().visProject} adapterName={this.adapterName} instance={this.instance} selectedWidgets={this.state.selectedWidgets} @@ -1019,7 +1022,7 @@ class Runtime extends GenericApp { { - !this.state.loaded || !this.state.visProject ? + !this.state.loaded || !store.getState().visProject.___settings ? : this.getVisEngine() } diff --git a/src/src/Store.tsx b/src/src/Store.tsx new file mode 100644 index 000000000..380eb78fd --- /dev/null +++ b/src/src/Store.tsx @@ -0,0 +1,62 @@ +import { createReducer, configureStore, createAction } from '@reduxjs/toolkit'; + +interface ProjectSettings { + darkReloadScreen: boolean; + destroyViewsAfter: number; + folders: {id: string, name: string, parentId: string}[]; + openedViews: string[]; + reconnectInterval: number; + reloadOnEdit: boolean; + reloadOnSleep: number; + statesDebounceTime: number; +} + +interface Widget { + data: Record; + style: Record; + tpl: string; + widgetSet: string; +} + +interface View { + activeWidgets: string[]; + filterList: string[]; + rerender: boolean; + settings: Record; + widgets: Record; +} + +interface Project { + // @ts-expect-error this type has bad code-style, we should refactor the views in a views: Record attribute + ___settings: ProjectSettings; + [view: string]: View; +} +export const updateProject = createAction('project/update'); +export const updateView = createAction<{viewId: string, data: View}>('view/update'); +export const updateWidget = createAction<{viewId: string, widgetId: string, data: Widget}>('widget/update'); + +const reducer = createReducer( + { + visProject: {} as Project, + }, + builder => { + builder + .addCase(updateProject, (state, action) => { + state.visProject = action.payload as Project; + }) + .addCase(updateView, (state, action) => { + const { viewId, data } = action.payload; + state.visProject[viewId] = data; + }) + .addCase(updateWidget, (state, action) => { + const { viewId, widgetId, data } = action.payload; + state.visProject[viewId].widgets[widgetId] = data; + }); + }, +); + +export const store = configureStore({ + reducer, +}); + +store.dispatch(updateProject); diff --git a/src/src/Toolbar/ToolbarItems.jsx b/src/src/Toolbar/ToolbarItems.jsx index 92c9a7e3e..2d29a30f9 100644 --- a/src/src/Toolbar/ToolbarItems.jsx +++ b/src/src/Toolbar/ToolbarItems.jsx @@ -20,6 +20,8 @@ import { I18n } from '@iobroker/adapter-react-v5'; import MultiSelect from './MultiSelect'; +import { store } from '../Store'; + const styles = theme => ({ toolbarBlock: { display: 'flex', @@ -52,7 +54,8 @@ const styles = theme => ({ }); const getItem = (item, key, props, full) => { - const view = props.project[props.selectedView]; + const { visProject } = store.getState(); + const view = visProject[props.selectedView]; if (!item || item.hide) { !item && console.warn(`Strange item: ${key}`); @@ -68,7 +71,7 @@ const getItem = (item, key, props, full) => { if (!item.field) { return; } - const project = JSON.parse(JSON.stringify(props.project)); + const project = JSON.parse(JSON.stringify(visProject)); project[props.selectedView].settings[item.field] = changeValue; props.changeProject(project); diff --git a/src/src/Toolbar/Views.jsx b/src/src/Toolbar/Views.jsx index eece2e510..68c4a4034 100644 --- a/src/src/Toolbar/Views.jsx +++ b/src/src/Toolbar/Views.jsx @@ -15,6 +15,7 @@ import ViewsManager from './ViewsManager'; import ToolbarItems from './ToolbarItems'; import ViewDialog from './ViewsManager/ViewDialog'; +import { store } from '../Store'; const styles = () => ({ label: { @@ -101,7 +102,7 @@ const Views = props => { setDialogName={setDialogName} setDialogParentId={setDialogParentId} classes={{}} - {...props} + project={store.getState().visProject} /> ; }; diff --git a/src/src/Toolbar/ViewsManager/index.jsx b/src/src/Toolbar/ViewsManager/index.jsx index 5bbd1cab3..08d599cff 100644 --- a/src/src/Toolbar/ViewsManager/index.jsx +++ b/src/src/Toolbar/ViewsManager/index.jsx @@ -21,6 +21,7 @@ import ExportDialog from './ExportDialog'; import ImportDialog from './ImportDialog'; import FolderDialog from './FolderDialog'; import { DndPreview, isTouchDevice } from '../../Utils'; +import { store } from '../../Store'; const styles = theme => ({ viewManageButtonActions: theme.classes.viewManageButtonActions, @@ -74,20 +75,22 @@ const ViewsManager = props => { } }, []); + const { visProject } = store.getState(); + const moveFolder = (id, parentId) => { - const project = JSON.parse(JSON.stringify(props.project)); + const project = JSON.parse(JSON.stringify(visProject)); project.___settings.folders.find(folder => folder.id === id).parentId = parentId; props.changeProject(project); }; const moveView = (name, parentId) => { - const project = JSON.parse(JSON.stringify(props.project)); + const project = JSON.parse(JSON.stringify(visProject)); project[name].parentId = parentId; props.changeProject(project); }; const importViewAction = (view, data) => { - const project = JSON.parse(JSON.stringify(props.project)); + const project = JSON.parse(JSON.stringify(visProject)); const viewObject = JSON.parse(data); if (!viewObject || !viewObject.settings || !viewObject.widgets || !viewObject.activeWidgets) { return; @@ -97,9 +100,9 @@ const ViewsManager = props => { props.changeProject(project); }; - const renderViews = parentId => Object.keys(props.project) + const renderViews = parentId => Object.keys(visProject) .filter(name => !name.startsWith('___')) - .filter(name => (parentId ? props.project[name].parentId === parentId : !props.project[name].parentId)) + .filter(name => (parentId ? visProject[name].parentId === parentId : !visProject[name].parentId)) .sort((name1, name2) => (name1.toLowerCase() < name2.toLowerCase() ? 0 : 1)) .map((name, key) =>
{
); const renderFolders = parentId => { - const folders = props.project.___settings.folders + const folders = visProject.___settings.folders .filter(folder => (parentId ? folder.parentId === parentId : !folder.parentId)); return folders.map((folder, key) =>
@@ -212,14 +215,14 @@ const ViewsManager = props => { onClose={() => setImportDialog(false)} view={importDialog || ''} importViewAction={importViewAction} - project={props.project} + project={visProject} themeName={props.themeName} /> : null} {exportDialog !== false ? setExportDialog(false)} view={exportDialog || ''} - project={props.project} + project={visProject} themeName={props.themeName} /> : null} ; @@ -231,7 +234,6 @@ ViewsManager.propTypes = { name: PropTypes.string, onClose: PropTypes.func, open: PropTypes.bool, - project: PropTypes.object, showDialog: PropTypes.func, themeName: PropTypes.string, toggleView: PropTypes.func, diff --git a/src/src/Toolbar/Widgets.jsx b/src/src/Toolbar/Widgets.jsx index 684f57d7c..cfd792068 100644 --- a/src/src/Toolbar/Widgets.jsx +++ b/src/src/Toolbar/Widgets.jsx @@ -25,6 +25,7 @@ import { } from '@mui/icons-material'; import { I18n } from '@iobroker/adapter-react-v5'; +import { store } from '../Store'; import ToolbarItems from './ToolbarItems'; import { getWidgetTypes } from '../Vis/visWidgetsCatalog'; @@ -38,17 +39,19 @@ const Widgets = props => { const [filterDialog, setFilterDialog] = useState(false); const toolbar = useMemo(() => { + const project = store.getState().visProject; + if (!props.widgetsLoaded) { return null; } if (!props.openedViews.length) { return null; } - if (!props.project[props.selectedView]) { + if (!project[props.selectedView]) { return null; } const widgetTypes = getWidgetTypes(); - const widgets = props.project[props.selectedView].widgets; + const widgets = project[props.selectedView].widgets; const shownWidgets = Object.keys(widgets) .filter(widget => (props.selectedGroup ? @@ -62,7 +65,7 @@ const Widgets = props => { type: 'icon-button', Icon: FilterIcon, name: 'Filter widgets', - color: props.project[props.selectedView].filterWidgets?.length ? '#c00000' : undefined, + color: project[props.selectedView].filterWidgets?.length ? '#c00000' : undefined, disabled: !props.editMode, onClick: () => setFilterDialog(true), }, @@ -358,7 +361,7 @@ const Widgets = props => { props.history.length, props.widgetsLoaded, props.openedViews.length, - props.project[props.selectedView], + store.getState().visProject[props.selectedView], ]); if (!props.widgetsLoaded) { @@ -367,7 +370,7 @@ const Widgets = props => { if (!props.openedViews.length) { return null; } - if (!props.project[props.selectedView]) { + if (!store.getState().visProject[props.selectedView]) { return null; } @@ -377,13 +380,13 @@ const Widgets = props => { onClose={() => setImportDialog(false)} changeProject={props.changeProject} selectedView={props.selectedView} - project={props.project} + project={store.getState().visProject} themeType={props.themeType} getNewWidgetIdNumber={props.getNewWidgetIdNumber} /> : null} {exportDialog ? setExportDialog(false)} - widgets={props.project[props.selectedView].widgets} + widgets={store.getState().visProject[props.selectedView].widgets} selectedWidgets={props.selectedWidgets} themeType={props.themeType} /> : null} @@ -391,7 +394,7 @@ const Widgets = props => { onClose={() => setFilterDialog(false)} changeProject={props.changeProject} selectedView={props.selectedView} - project={props.project} + project={store.getState().visProject} /> : null} ; }; @@ -399,7 +402,6 @@ const Widgets = props => { Widgets.propTypes = { openedViews: PropTypes.array, themeType: PropTypes.string, - project: PropTypes.object, selectedView: PropTypes.string, selectedWidgets: PropTypes.array, setSelectedWidgets: PropTypes.func, diff --git a/src/src/Vis/visUtils.jsx b/src/src/Vis/visUtils.jsx index ec42732e6..dca744887 100644 --- a/src/src/Vis/visUtils.jsx +++ b/src/src/Vis/visUtils.jsx @@ -13,6 +13,7 @@ * (Free for non-commercial use). */ import { I18n } from '@iobroker/adapter-react-v5'; +import { store, updateWidget } from '../Store'; function replaceGroupAttr(inputStr, groupAttrList) { let newString = inputStr; @@ -356,10 +357,14 @@ function getUsedObjectIDsInWidget(views, view, wid, linkContext) { // if widget is in the group => replace groupAttrX values if (widget.grouped) { - widget.groupid = widget.groupid || getWidgetGroup(views, view, wid); + console.log(store.getState().visProject); + if (!widget.groupid) { + store.dispatch(updateWidget({ viewId: view, widgetId: widget, data: { ...widget, groupid: getWidgetGroup(views, view, wid) } })); + } + // widget.groupid = widget.groupid || getWidgetGroup(views, view, wid); - if (!views[view].widgets[widget.groupid]) { - widget.groupid = getWidgetGroup(views, view, wid); + if (!store.getState().visProject[view].widgets[widget.groupid]) { + store.dispatch(updateWidget({ viewId: view, widgetId: widget, data: { ...widget, groupid: getWidgetGroup(views, view, wid) } })); if (!widget.groupid) { // create a fictive group let groupNum = 1; diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 000000000..2bb6395c2 --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "checkJs": true, + "noEmit": false, + "outDir": "./build", + "sourceMap": true, + "sourceRoot": "./src", + "noImplicitAny": true, + "lib": [ + "es2018", + "DOM" + ], + "jsx": "react", + "types": [ + "@iobroker/types" + ], + "paths": { + "@/*": [ + "./src/*" + ] + } + }, + "include": [ + "./**/*.ts", + "./**/*.tsx", + ], + "exclude": [ + "./**/*.test.ts", + "./**/*.test.tsx" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..358f6a885 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +// Root tsconfig to set the settings and power editor support for all TS files +{ + "extends": "@tsconfig/node16/tsconfig.json", + "compilerOptions": { + // do not compile anything, this file is just to configure type checking + "noEmit": true, + + // check JS files + "allowJs": true, + "checkJs": true, + + // This is necessary for the automatic typing of the adapter config + "resolveJsonModule": true, + + // If you want to disable the stricter type checks (not recommended), uncomment the following line + // "strict": false, + // And enable some of those features for more fine-grained control + // "strictNullChecks": true, + // "strictPropertyInitialization": true, + // "strictBindCallApply": true, + "noImplicitAny": false, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + "useUnknownInCatchVariables": false, + }, + "include": [ + "**/*.js", + "**/*.d.ts" + ], + "exclude": [ + "node_modules/**", + "widgets/**" + ] +} \ No newline at end of file