Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed multiple issues with cloning/copying groups #194

Merged
merged 8 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ E.g., if it was used in a menu and the menu is red, the circle would be red.
* (foxriver76) sort folders alphabetically in pages view
* (foxriver76) fixed deselecting widgets with ctrl + click
* (foxriver76) fixed display issue with switch component
* (bleufox) implemented Basic Red Number widget natively
* (bluefox) implemented Basic Red Number widget natively
* (foxriver76) fixed copy/clone of grouped widgets

### 2.7.0 (2023-11-22)
* (foxriver76) implemented Basic Bar widget natively
Expand Down
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export default BulkEditor;
fs.writeFileSync(`${__dirname}/runtime/src/App.jsx`, fs.readFileSync(`${__dirname}/src/src/Runtime.jsx`));
fs.writeFileSync(`${__dirname}/runtime/tsconfig.json`, fs.readFileSync(`${__dirname}/src/tsconfig.json`));
fs.writeFileSync(`${__dirname}/runtime/src/Store.tsx`, fs.readFileSync(`${__dirname}/src/src/Store.tsx`));
fs.writeFileSync(`${__dirname}/runtime/src/types.d.ts`, fs.readFileSync(`${__dirname}/src/src/types.d.ts`));
fs.writeFileSync(`${__dirname}/runtime/src/serviceWorker.jsx`, fs.readFileSync(`${__dirname}/src/src/serviceWorker.jsx`));
fs.writeFileSync(`${__dirname}/runtime/src/index.jsx`, fs.readFileSync(`${__dirname}/src/src/index.jsx`));
fs.writeFileSync(`${__dirname}/runtime/src/theme.jsx`, fs.readFileSync(`${__dirname}/src/src/theme.jsx`));
Expand Down
1 change: 1 addition & 0 deletions src/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,6 @@ module.exports = {
radix: 'off',
indent: ['error', 4, { SwitchCase: 1 }],
'no-alert': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
4 changes: 4 additions & 0 deletions src/craco.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// const CracoEsbuildPlugin = require('craco-esbuild');
const { ProvidePlugin } = require('webpack');
const path = require('path');
const cracoModuleFederation = require('@iobroker/adapter-react-v5/craco-module-federation');

module.exports = {
Expand All @@ -21,6 +22,9 @@ module.exports = {
},
},
webpack: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
output: {
publicPath: './',
},
Expand Down
2 changes: 1 addition & 1 deletion src/public/material-icons/baseline.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/public/material-icons/outline.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/public/material-icons/round.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/public/material-icons/sharp.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/public/material-icons/twotone.json

Large diffs are not rendered by default.

92 changes: 20 additions & 72 deletions src/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ import {
Message as MessageDialog,
SelectFile as SelectFileDialog, Icon,
} from '@iobroker/adapter-react-v5';
import {
isGroup, deepClone, getNewWidgetId, getNewGroupId, copyGroup,
} from './Utils/utils';
import { recalculateFields, store, updateProject } from './Store';

import Attributes from './Attributes';
Expand Down Expand Up @@ -368,64 +371,10 @@ class App extends Runtime {
return selectedWidgets;
}

/**
* Get next widgetId as a number
*
* @param isGroup if it is a group of widgets
* @param project current project
* @param offset offset if multiple widgets are created and not yet in project
* @return {number}
*/
getNewWidgetIdNumber = (isGroup, project, offset = 0) => {
const widgets = [];
project = project || store.getState().visProject;
Object.keys(project).forEach(view =>
project[view].widgets && Object.keys(project[view].widgets).forEach(widget =>
widgets.push(widget)));
let newKey = 1;
widgets.forEach(name => {
const matches = isGroup ? name.match(/^g([0-9]+)$/) : name.match(/^w([0-9]+)$/);
if (matches) {
const num = parseInt(matches[1], 10);
if (num >= newKey) {
newKey = num + 1;
}
}
});

return newKey + offset;
};

/**
* Get new widget id from the project
* @param project project to determine next widget id for
* @param offset offset, if multiple widgets are created and not yet in the project
* @return {string}
*/
getNewWidgetId = (project, offset = 0) => {
const newKey = this.getNewWidgetIdNumber(false, project, offset);

return `w${(newKey).toString().padStart(6, 0)}`;
};

/**
* Get new group id from the project
* @param project project to determine next group id for
* @param offset offset, if multiple groups are created and not yet in the project
* @return {string}
*/
getNewGroupId = (project, offset = 0) => {
let newKey = this.getNewWidgetIdNumber(true, project, offset);

newKey = `g${newKey.toString().padStart(6, 0)}`;

return newKey;
};

addWidget = async (widgetType, x, y, data, style) => {
const project = JSON.parse(JSON.stringify(store.getState().visProject));
const widgets = project[this.state.selectedView].widgets;
const newKey = this.getNewWidgetId();
const newKey = getNewWidgetId(store.getState().visProject);
widgets[newKey] = {
tpl: widgetType,
data: {
Expand Down Expand Up @@ -652,11 +601,12 @@ class App extends Runtime {
}
let newKey;

if (newWidget.tpl === '_tplGroup') {
newKey = this.getNewGroupId(store.getState().visProject, groupOffset);
if (isGroup(newWidget)) {
copyGroup({ group: newWidget, widgets, offset: groupOffset });
newKey = getNewGroupId(store.getState().visProject, groupOffset);
groupOffset++;
} else {
newKey = this.getNewWidgetId(store.getState().visProject, widgetOffset);
newKey = getNewWidgetId(store.getState().visProject, widgetOffset);
widgetOffset++;
}

Expand All @@ -681,9 +631,14 @@ class App extends Runtime {
left: boundingRect.left + 10,
top: boundingRect.top + 10,
});
const newKey = this.getNewWidgetId();
widgets[newKey] = newWidget;
newKeys.push(newKey);

if (isGroup(newWidget)) {
copyGroup({ group: newWidget, widgets });
} else {
const newKey = getNewWidgetId(store.getState().visProject);
widgets[newKey] = newWidget;
newKeys.push(newKey);
}
});
this.setSelectedWidgets([]);
await this.changeProject(project);
Expand Down Expand Up @@ -916,7 +871,7 @@ class App extends Runtime {

},
};
const groupId = this.getNewGroupId();
const groupId = getNewGroupId();
let left = 0;
let top = 0;
let right = 0;
Expand Down Expand Up @@ -1317,16 +1272,14 @@ class App extends Runtime {
};

importMarketplaceWidget = (project, view, widgets, id, x, y, widgetId, oldData, oldStyle) => {
let newKeyNumber = this.getNewWidgetIdNumber();
let newGroupKeyNumber = this.getNewWidgetIdNumber(true);
const newWidgets = {};

widgets.forEach(_widget => {
if (_widget.isRoot) {
_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')}`;
if (isGroup(_widget)) {
let newKey = getNewGroupId(store.getState().visProject);
if (_widget.isRoot) {
if (widgetId) {
newKey = widgetId;
Expand All @@ -1347,10 +1300,8 @@ class App extends Runtime {
w.groupid = newKey;
}
} while (w);

newGroupKeyNumber++;
} else {
const newKey = `w${newKeyNumber.toString().padStart(6, '0')}`;
const newKey = getNewWidgetId(store.getState().visProject);
newWidgets[newKey] = _widget;
if (_widget.grouped && newWidgets[_widget.groupid] && newWidgets[_widget.groupid].data && newWidgets[_widget.groupid].data.members) {
// find group
Expand All @@ -1359,7 +1310,6 @@ class App extends Runtime {
newWidgets[_widget.groupid].data.members[pos] = newKey;
}
}
newKeyNumber++;
}
});

Expand Down Expand Up @@ -1626,7 +1576,6 @@ class App extends Runtime {
project={store.getState().visProject}
selectedView={this.state.selectedView}
changeProject={this.changeProject}
getNewWidgetIdNumber={this.getNewWidgetIdNumber}
lockWidgets={this.lockWidgets}
groupWidgets={this.groupWidgets}
ungroupWidgets={this.ungroupWidgets}
Expand Down Expand Up @@ -1999,7 +1948,6 @@ class App extends Runtime {
alignWidgets={this.alignWidgets}
cloneWidgets={this.cloneWidgets}
orderWidgets={this.orderWidgets}
getNewWidgetIdNumber={this.getNewWidgetIdNumber}
lockDragging={this.state.lockDragging}
disableInteraction={this.state.disableInteraction}
toggleLockDragging={this.toggleLockDragging}
Expand Down
22 changes: 0 additions & 22 deletions src/src/Components/WizardHelpers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,30 +276,8 @@ const detectDevices = async socket => {
return result;
};

const getNewWidgetIdNumber = project => {
const widgetIDs = [];
Object.keys(project).forEach(view =>
project[view].widgets && Object.keys(project[view].widgets).forEach(widget =>
widgetIDs.push(widget)));
let newKey = 1;

// find max key
widgetIDs.forEach(wid => {
const matches = wid.match(/^w([0-9]+)$/);
if (matches) {
const num = parseInt(matches[1], 10);
if (num >= newKey) {
newKey = num + 1;
}
}
});

return newKey;
};

export default {
deviceIcons,
getNewWidgetIdNumber,
detectDevices,
getObjectIcon,
allObjects,
Expand Down
32 changes: 1 addition & 31 deletions src/src/Store.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,6 @@
import { createReducer, configureStore, createAction } from '@reduxjs/toolkit';
import type { View, Widget, Project } from '@/types';

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<string, unknown>;
style: Record<string, unknown>;
tpl: string;
widgetSet: string;
}

interface View {
activeWidgets: string[];
filterList: string[];
rerender: boolean;
settings: Record<string, unknown>;
widgets: Record<string, Widget>;
}

interface Project {
// @ts-expect-error this type has bad code-style, we should refactor the views in a views: Record<string, View> attribute
___settings: ProjectSettings;
[view: string]: View;
}
export const updateProject = createAction<Project>('project/update');
export const updateView = createAction<{viewId: string, data: View}>('view/update');
export const updateWidget = createAction<{viewId: string, widgetId: string, data: Widget}>('widget/update');
Expand Down
15 changes: 6 additions & 9 deletions src/src/Toolbar/WidgetImportDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { Close as CloseIcon, ImportExport } from '@mui/icons-material';

import { I18n } from '@iobroker/adapter-react-v5';
import { isGroup, getNewGroupId, getNewWidgetId } from '../Utils/utils';

import { useFocus } from '../Utils';
import CustomAceEditor from '../Components/CustomAceEditor';
Expand All @@ -22,15 +23,14 @@ const WidgetImportDialog = props => {
const editor = useRef(null);

const importWidgets = () => {
const project = JSON.parse(JSON.stringify(store.getState().visProject));
const { visProject } = store.getState();
const project = JSON.parse(JSON.stringify(visProject));
const widgets = JSON.parse(data);
let newKeyNumber = props.getNewWidgetIdNumber();
let newGroupKeyNumber = props.getNewWidgetIdNumber(true);
const newWidgets = {};

widgets.forEach(widget => {
if (widget.tpl === '_tplGroup') {
const newKey = `g${newGroupKeyNumber.toString().padStart(6, '0')}`;
if (isGroup(widget)) {
const newKey = getNewGroupId(visProject);
newWidgets[newKey] = widget;
// find all widgets that belong to this group and change groupid
let w;
Expand All @@ -40,10 +40,8 @@ const WidgetImportDialog = props => {
w.groupid = newKey;
}
} while (w);

newGroupKeyNumber++;
} else {
const newKey = `w${newKeyNumber.toString().padStart(6, '0')}`;
const newKey = getNewWidgetId(visProject);
newWidgets[newKey] = widget;
if (widget.grouped && newWidgets[widget.groupid] && newWidgets[widget.groupid].data && newWidgets[widget.groupid].data.members) {
// find group
Expand All @@ -52,7 +50,6 @@ const WidgetImportDialog = props => {
newWidgets[widget.groupid].data.members[pos] = newKey;
}
}
newKeyNumber++;
}
});

Expand Down
1 change: 0 additions & 1 deletion src/src/Toolbar/Widgets.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,6 @@ const Widgets = props => {
selectedView={props.selectedView}
project={store.getState().visProject}
themeType={props.themeType}
getNewWidgetIdNumber={props.getNewWidgetIdNumber}
/> : null}
{exportDialog ? <WidgetExportDialog
onClose={() => setExportDialog(false)}
Expand Down
Loading
Loading