From 8899f55bd0239e53ceb0d34e36e721ae25277aee Mon Sep 17 00:00:00 2001 From: Sina Blattmann Date: Thu, 9 Mar 2023 15:25:57 +0100 Subject: [PATCH] Finish dnd treeview, add mobile support for scrolling in dnd treeview --- typescript/Frontend/package-lock.json | 72 +++++++++++++++++ typescript/Frontend/package.json | 1 + typescript/Frontend/src/Login.tsx | 22 +++-- .../src/components/Groups/FolderForm.tsx | 8 +- .../Frontend/src/components/Groups/Groups.tsx | 2 +- .../Groups/{ => Tree}/DragPreview.module.scss | 0 .../Groups/{ => Tree}/DragPreview.tsx | 4 +- .../Groups/{ => Tree}/GroupTree.module.scss | 5 ++ .../Groups/{ => Tree}/GroupTree.tsx | 80 +++++++++++-------- .../Groups/{ => Tree}/TreeNode.module.scss | 9 +++ .../components/Groups/{ => Tree}/TreeNode.tsx | 11 ++- .../components/Groups/{ => Tree}/TypeIcon.tsx | 0 12 files changed, 165 insertions(+), 49 deletions(-) rename typescript/Frontend/src/components/Groups/{ => Tree}/DragPreview.module.scss (100%) rename typescript/Frontend/src/components/Groups/{ => Tree}/DragPreview.tsx (90%) rename typescript/Frontend/src/components/Groups/{ => Tree}/GroupTree.module.scss (77%) rename typescript/Frontend/src/components/Groups/{ => Tree}/GroupTree.tsx (52%) rename typescript/Frontend/src/components/Groups/{ => Tree}/TreeNode.module.scss (83%) rename typescript/Frontend/src/components/Groups/{ => Tree}/TreeNode.tsx (79%) rename typescript/Frontend/src/components/Groups/{ => Tree}/TypeIcon.tsx (100%) diff --git a/typescript/Frontend/package-lock.json b/typescript/Frontend/package-lock.json index 36f44c775..1bcf739ce 100644 --- a/typescript/Frontend/package-lock.json +++ b/typescript/Frontend/package-lock.json @@ -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", diff --git a/typescript/Frontend/package.json b/typescript/Frontend/package.json index a210524d3..803841d8f 100644 --- a/typescript/Frontend/package.json +++ b/typescript/Frontend/package.json @@ -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", diff --git a/typescript/Frontend/src/Login.tsx b/typescript/Frontend/src/Login.tsx index d2448a1ed..6ec794962 100644 --- a/typescript/Frontend/src/Login.tsx +++ b/typescript/Frontend/src/Login.tsx @@ -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); diff --git a/typescript/Frontend/src/components/Groups/FolderForm.tsx b/typescript/Frontend/src/components/Groups/FolderForm.tsx index 0f5d49fc2..2dac7aec6 100644 --- a/typescript/Frontend/src/components/Groups/FolderForm.tsx +++ b/typescript/Frontend/src/components/Groups/FolderForm.tsx @@ -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); }); diff --git a/typescript/Frontend/src/components/Groups/Groups.tsx b/typescript/Frontend/src/components/Groups/Groups.tsx index 2f56c2850..3fd71db92 100644 --- a/typescript/Frontend/src/components/Groups/Groups.tsx +++ b/typescript/Frontend/src/components/Groups/Groups.tsx @@ -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 ( diff --git a/typescript/Frontend/src/components/Groups/DragPreview.module.scss b/typescript/Frontend/src/components/Groups/Tree/DragPreview.module.scss similarity index 100% rename from typescript/Frontend/src/components/Groups/DragPreview.module.scss rename to typescript/Frontend/src/components/Groups/Tree/DragPreview.module.scss diff --git a/typescript/Frontend/src/components/Groups/DragPreview.tsx b/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx similarity index 90% rename from typescript/Frontend/src/components/Groups/DragPreview.tsx rename to typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx index 030ee14d9..a27d707a9 100644 --- a/typescript/Frontend/src/components/Groups/DragPreview.tsx +++ b/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx @@ -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; diff --git a/typescript/Frontend/src/components/Groups/GroupTree.module.scss b/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss similarity index 77% rename from typescript/Frontend/src/components/Groups/GroupTree.module.scss rename to typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss index 057d07037..11d791fb0 100644 --- a/typescript/Frontend/src/components/Groups/GroupTree.module.scss +++ b/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss @@ -18,3 +18,8 @@ .dropTarget { background-color: #e8f0fe; } + +.treeContainer { + height: 500px; + overflow: auto; +} diff --git a/typescript/Frontend/src/components/Groups/GroupTree.tsx b/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx similarity index 52% rename from typescript/Frontend/src/components/Groups/GroupTree.tsx rename to typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx index 53e565cb5..c5b9ffe9d 100644 --- a/typescript/Frontend/src/components/Groups/GroupTree.tsx +++ b/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx @@ -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 ) => { 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 ( - - tree={getTreeData(data)} - rootId={0} - dragPreviewRender={(monitorProps) => ( - - )} - classes={{ - container: styles.tree, - root: styles.treeRoot, - draggingSource: styles.draggingSource, - dropTarget: styles.dropTarget, - }} - render={( - node: NodeModel, - { depth, isOpen, onToggle, hasChild } - ) => ( - - )} - onDrop={( - tree: NodeModel[], - options: DropOptions - ) => handleDrop(tree, options)} - /> + + + tree={getTreeData(data)} + rootId={0} + dragPreviewRender={(monitorProps) => ( + + )} + classes={{ + container: styles.tree, + root: styles.treeRoot, + draggingSource: styles.draggingSource, + dropTarget: styles.dropTarget, + }} + render={( + node: NodeModel, + { depth, isOpen, onToggle, hasChild, handleRef } + ) => ( + + )} + onDrop={( + tree: NodeModel[], + options: DropOptions + ) => handleDrop(tree, options)} + /> + ); } diff --git a/typescript/Frontend/src/components/Groups/TreeNode.module.scss b/typescript/Frontend/src/components/Groups/Tree/TreeNode.module.scss similarity index 83% rename from typescript/Frontend/src/components/Groups/TreeNode.module.scss rename to typescript/Frontend/src/components/Groups/Tree/TreeNode.module.scss index 4f8b0f0c8..e36b7be8c 100644 --- a/typescript/Frontend/src/components/Groups/TreeNode.module.scss +++ b/typescript/Frontend/src/components/Groups/Tree/TreeNode.module.scss @@ -25,3 +25,12 @@ .labelGridItem { padding-inline-start: 8px; } + +.handle { + cursor: grab; + display: flex; +} + +.handle > svg { + pointer-events: none; +} diff --git a/typescript/Frontend/src/components/Groups/TreeNode.tsx b/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx similarity index 79% rename from typescript/Frontend/src/components/Groups/TreeNode.tsx rename to typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx index dc1f50d25..3080cab71 100644 --- a/typescript/Frontend/src/components/Groups/TreeNode.tsx +++ b/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx @@ -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; @@ -14,10 +15,11 @@ type Props = { isOpen: boolean; onToggle: (id: NodeModel["id"]) => void; hasChild: boolean; + handleRef: React.RefObject; }; const TreeNode: React.FC = (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) => { {node.text} +
+ +
); }; diff --git a/typescript/Frontend/src/components/Groups/TypeIcon.tsx b/typescript/Frontend/src/components/Groups/Tree/TypeIcon.tsx similarity index 100% rename from typescript/Frontend/src/components/Groups/TypeIcon.tsx rename to typescript/Frontend/src/components/Groups/Tree/TypeIcon.tsx