Finish dnd treeview, add mobile support for scrolling in dnd treeview

This commit is contained in:
Sina Blattmann 2023-03-09 15:25:57 +01:00
parent 53346740ab
commit 8899f55bd0
12 changed files with 165 additions and 49 deletions

View File

@ -30,6 +30,7 @@
"react-chartjs-2": "^5.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-scrolling": "^1.3.3",
"react-dom": "^18.2.0",
"react-intl": "^6.2.10",
"react-router-dom": "^6.8.0",
@ -6551,6 +6552,14 @@
"wrap-ansi": "^7.0.0"
}
},
"node_modules/clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
@ -7435,6 +7444,17 @@
"node": ">= 10"
}
},
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"dependencies": {
"clone": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@ -12913,6 +12933,11 @@
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="
},
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
},
"node_modules/lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -15490,6 +15515,23 @@
"dnd-core": "^16.0.1"
}
},
"node_modules/react-dnd-scrolling": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/react-dnd-scrolling/-/react-dnd-scrolling-1.3.3.tgz",
"integrity": "sha512-IEUbvK7fPWzOpPXaMvYQJR+ZGDPUtOwNpAJeEqUdTqQb0quwaytr/LfiAFGbWI87D55InsGaTUDyWkfXURyLNA==",
"dependencies": {
"defaults": "^1.0.4",
"hoist-non-react-statics": "3.x",
"lodash.throttle": "^4.1.1",
"prop-types": "15.x",
"raf": "^3.4.1"
},
"peerDependencies": {
"react": "16.x || 17.x || 18.x",
"react-dnd": "10.x || 11.x || 14.x || 15.x || 16.x",
"react-dom": "16.x || 17.x || 18.x"
}
},
"node_modules/react-dnd-touch-backend": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-16.0.1.tgz",
@ -23257,6 +23299,11 @@
"wrap-ansi": "^7.0.0"
}
},
"clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="
},
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
@ -23894,6 +23941,14 @@
"execa": "^5.0.0"
}
},
"defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"requires": {
"clone": "^1.0.2"
}
},
"define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@ -27886,6 +27941,11 @@
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -29561,6 +29621,18 @@
"dnd-core": "^16.0.1"
}
},
"react-dnd-scrolling": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/react-dnd-scrolling/-/react-dnd-scrolling-1.3.3.tgz",
"integrity": "sha512-IEUbvK7fPWzOpPXaMvYQJR+ZGDPUtOwNpAJeEqUdTqQb0quwaytr/LfiAFGbWI87D55InsGaTUDyWkfXURyLNA==",
"requires": {
"defaults": "^1.0.4",
"hoist-non-react-statics": "3.x",
"lodash.throttle": "^4.1.1",
"prop-types": "15.x",
"raf": "^3.4.1"
}
},
"react-dnd-touch-backend": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-16.0.1.tgz",

View File

@ -25,6 +25,7 @@
"react-chartjs-2": "^5.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-scrolling": "^1.3.3",
"react-dom": "^18.2.0",
"react-intl": "^6.2.10",
"react-router-dom": "^6.8.0",

View File

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { Alert, Button, CircularProgress, Grid } from "@mui/material";
import Container from "@mui/material/Container";
import { axiosConfigWithoutToken } from "./config/axiosConfig";
import axiosConfig, { axiosConfigWithoutToken } from "./config/axiosConfig";
import InnovenergyTextfield from "./components/Layout/InnovenergyTextfield";
const loginUser = async (username: string, password: string) => {
@ -11,18 +11,30 @@ const loginUser = async (username: string, password: string) => {
});
};
const Login = ({ setToken }: { setToken: any }) => {
const Login = ({ setToken }: { setToken: (value: string) => void }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const handleSubmit = (e: any) => {
const verifyToken = async () => {
axiosConfig.get("/GetAllInstallations");
};
const handleSubmit = () => {
setLoading(true);
loginUser(username, password).then(({ data }) => {
// TODO change this if they return err codes from backend
if (typeof data === "string") {
setToken(data);
setLoading(false);
verifyToken()
.then(() => {
setToken(data);
setLoading(false);
})
.catch((err) => {
setError(err);
setLoading(false);
});
}
setError(data);
setLoading(false);

View File

@ -8,10 +8,10 @@ import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
interface I_CustomerFormProps {
values: I_Folder;
id: string | undefined;
id: string;
}
const updateFolder = (data: any) => {
const updateFolder = (data: I_Folder) => {
return axiosConfig.put("/UpdateFolder", data);
};
const FolderForm = (props: I_CustomerFormProps) => {
@ -26,9 +26,11 @@ const FolderForm = (props: I_CustomerFormProps) => {
information: values.information,
},
onSubmit: (formikValues) => {
const idAsNumber = parseInt(id, 10);
updateFolder({
...values,
...formikValues,
id,
id: idAsNumber,
}).then((res) => {
setSnackbarOpen(true);
});

View File

@ -6,7 +6,7 @@ import InstallationDetail from "../Installations/Installation";
import NavigationButtons from "../Layout/NavigationButtons";
import Folder from "./Folder";
import GroupTabs from "./GroupTabs";
import GroupTree from "./GroupTree";
import GroupTree from "./Tree/GroupTree";
const Groups = () => {
return (

View File

@ -1,7 +1,7 @@
import { DragLayerMonitorProps } from "@minoru/react-dnd-treeview";
import { I_Folder, I_Installation } from "../../util/types";
import TypeIcon from "./TypeIcon";
import { I_Installation, I_Folder } from "../../../util/types";
import styles from "./DragPreview.module.scss";
import TypeIcon from "./TypeIcon";
interface DragPreviewProps {
monitorProps: DragLayerMonitorProps<I_Installation | I_Folder>;

View File

@ -18,3 +18,8 @@
.dropTarget {
background-color: #e8f0fe;
}
.treeContainer {
height: 500px;
overflow: auto;
}

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import axiosConfig from "../../config/axiosConfig";
import { I_Folder, I_Installation } from "../../util/types";
import axiosConfig from "../../../config/axiosConfig";
import { I_Folder, I_Installation } from "../../../util/types";
import { CircularProgress, Grid } from "@mui/material";
import { DndProvider } from "react-dnd";
import {
@ -12,6 +12,7 @@ import {
} from "@minoru/react-dnd-treeview";
import TreeNode from "./TreeNode";
import styles from "./GroupTree.module.scss";
import withScrolling from "react-dnd-scrolling";
import DragPreview from "./DragPreview";
const getTreeData = (
@ -32,6 +33,7 @@ const getTreeData = (
const GroupTree = () => {
const [data, setData] = useState<(I_Folder | I_Installation)[]>();
const [loading, setLoading] = useState(false);
const ScrollingComponent = withScrolling("div");
useEffect(() => {
getData();
@ -50,10 +52,15 @@ const GroupTree = () => {
{ dropTargetId, dragSource }: DropOptions<I_Folder | I_Installation>
) => {
axiosConfig
.put("/UpdateFolder", {
...dragSource?.data,
parentId: dropTargetId,
})
.put(
dragSource?.data?.type === "Folder"
? "/UpdateFolder"
: "/UpdateInstallation",
{
...dragSource?.data,
parentId: dropTargetId,
}
)
.then(() => {
getData();
});
@ -68,35 +75,38 @@ const GroupTree = () => {
} else if (data && data?.length > 1) {
return (
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
<Tree<I_Installation | I_Folder>
tree={getTreeData(data)}
rootId={0}
dragPreviewRender={(monitorProps) => (
<DragPreview monitorProps={monitorProps} />
)}
classes={{
container: styles.tree,
root: styles.treeRoot,
draggingSource: styles.draggingSource,
dropTarget: styles.dropTarget,
}}
render={(
node: NodeModel<I_Installation | I_Folder>,
{ depth, isOpen, onToggle, hasChild }
) => (
<TreeNode
node={node}
depth={depth}
isOpen={isOpen}
onToggle={onToggle}
hasChild={hasChild}
/>
)}
onDrop={(
tree: NodeModel<I_Folder | I_Installation>[],
options: DropOptions<I_Folder | I_Installation>
) => handleDrop(tree, options)}
/>
<ScrollingComponent className={styles.treeContainer}>
<Tree<I_Installation | I_Folder>
tree={getTreeData(data)}
rootId={0}
dragPreviewRender={(monitorProps) => (
<DragPreview monitorProps={monitorProps} />
)}
classes={{
container: styles.tree,
root: styles.treeRoot,
draggingSource: styles.draggingSource,
dropTarget: styles.dropTarget,
}}
render={(
node: NodeModel<I_Installation | I_Folder>,
{ depth, isOpen, onToggle, hasChild, handleRef }
) => (
<TreeNode
node={node}
depth={depth}
isOpen={isOpen}
onToggle={onToggle}
hasChild={hasChild}
handleRef={handleRef}
/>
)}
onDrop={(
tree: NodeModel<I_Folder | I_Installation>[],
options: DropOptions<I_Folder | I_Installation>
) => handleDrop(tree, options)}
/>
</ScrollingComponent>
</DndProvider>
);
}

View File

@ -25,3 +25,12 @@
.labelGridItem {
padding-inline-start: 8px;
}
.handle {
cursor: grab;
display: flex;
}
.handle > svg {
pointer-events: none;
}

View File

@ -4,9 +4,10 @@ import ArrowRightIcon from "@mui/icons-material/ArrowRight";
import { NodeModel } from "@minoru/react-dnd-treeview";
import styles from "./TreeNode.module.scss";
import { Link } from "react-router-dom";
import routes from "../../routes.json";
import { I_Folder, I_Installation } from "../../util/types";
import routes from "../../../routes.json";
import { I_Folder, I_Installation } from "../../../util/types";
import TypeIcon from "./TypeIcon";
import DragHandleIcon from "@mui/icons-material/DragHandle";
type Props = {
node: NodeModel<I_Installation | I_Folder>;
@ -14,10 +15,11 @@ type Props = {
isOpen: boolean;
onToggle: (id: NodeModel["id"]) => void;
hasChild: boolean;
handleRef: React.RefObject<any>;
};
const TreeNode: React.FC<Props> = (props) => {
const { node, isOpen, hasChild, onToggle, depth } = props;
const { node, isOpen, hasChild, onToggle, depth, handleRef } = props;
const indent = depth * 24;
const handleToggle = (e: React.MouseEvent) => {
@ -55,6 +57,9 @@ const TreeNode: React.FC<Props> = (props) => {
<Typography variant="body2">{node.text}</Typography>
</div>
</Link>
<div className={`${styles.handle} drag-handle`} ref={handleRef}>
<DragHandleIcon />
</div>
</div>
);
};