diff --git a/client/package-lock.json b/client/package-lock.json index 15e7897..61de325 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -75,6 +75,7 @@ "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -617,6 +618,7 @@ "integrity": "sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" } @@ -735,6 +737,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -1321,7 +1324,8 @@ "version": "4.30.4", "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.30.4.tgz", "integrity": "sha512-FCwsMvG9iiEs6uobdDrTaKsCgsqys0NuLgPPD8n37AYVYBiiDkrPkk9MSIU5rT2FahYcL1bScYI9huIPtlzqyA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fill-range": { "version": "7.0.1", @@ -1770,6 +1774,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -1795,6 +1800,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -1952,6 +1958,7 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -2007,6 +2014,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.1.tgz", "integrity": "sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==", "license": "MIT", + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -2180,6 +2188,7 @@ "integrity": "sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.15.9", "postcss": "^8.4.18", @@ -2256,6 +2265,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -2622,6 +2632,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.9.tgz", "integrity": "sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==", "devOptional": true, + "peer": true, "requires": { "@types/react": "*" } @@ -2702,6 +2713,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -3008,7 +3020,8 @@ "filepond": { "version": "4.30.4", "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.30.4.tgz", - "integrity": "sha512-FCwsMvG9iiEs6uobdDrTaKsCgsqys0NuLgPPD8n37AYVYBiiDkrPkk9MSIU5rT2FahYcL1bScYI9huIPtlzqyA==" + "integrity": "sha512-FCwsMvG9iiEs6uobdDrTaKsCgsqys0NuLgPPD8n37AYVYBiiDkrPkk9MSIU5rT2FahYcL1bScYI9huIPtlzqyA==", + "peer": true }, "fill-range": { "version": "7.0.1", @@ -3299,6 +3312,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -3316,6 +3330,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -3409,6 +3424,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "peer": true, "requires": { "@babel/runtime": "^7.9.2" } @@ -3447,6 +3463,7 @@ "version": "1.56.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.1.tgz", "integrity": "sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==", + "peer": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3556,6 +3573,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz", "integrity": "sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==", "dev": true, + "peer": true, "requires": { "esbuild": "^0.15.9", "fsevents": "~2.3.2", diff --git a/client/src/App.jsx b/client/src/App.jsx index 166b122..aa1930f 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -85,11 +85,9 @@ const App = () => { } path="dashboard" exact /> } path="profile" exact /> - } path="browse"> - } path=":code"> - } path=":folderId" /> - - + } path="browse" /> + } path="browse/:code" /> + } path="browse/:code/:folderId" /> } path="/" /> } path="*" /> diff --git a/client/src/screens/browse/components/browsefolder/index.jsx b/client/src/screens/browse/components/browsefolder/index.jsx index 5891440..aac9f54 100644 --- a/client/src/screens/browse/components/browsefolder/index.jsx +++ b/client/src/screens/browse/components/browsefolder/index.jsx @@ -9,6 +9,8 @@ import { ConfirmDialog } from "./confirmDialog"; import { FolderRename } from "./folderRename.jsx"; import { getSubtreeFileCount } from "../../../../utils/folderUtils"; +import { useNavigate } from "react-router-dom"; + const BrowseFolder = ({ name, subject, @@ -17,6 +19,7 @@ const BrowseFolder = ({ isMobileView = false, }) => { const dispatch = useDispatch(); + const navigate = useNavigate(); const currentFolder = useSelector((state) => state.fileBrowser.currentFolder); const isBR = useSelector((state) => state.user.user.isBR); const [showConfirm, setShowConfirm] = useState(false); @@ -58,6 +61,9 @@ const BrowseFolder = ({ dispatch(PushFolderHistory(currentFolder)); } dispatch(ChangeFolder(folderData)); + if (courseCode && folderData?._id) { + navigate(`/browse/${courseCode}/${folderData._id}`); + } }; const handleDelete = async (e) => { diff --git a/client/src/screens/browse/components/collapsible/components/folder/index.jsx b/client/src/screens/browse/components/collapsible/components/folder/index.jsx index 706797e..851f531 100644 --- a/client/src/screens/browse/components/collapsible/components/folder/index.jsx +++ b/client/src/screens/browse/components/collapsible/components/folder/index.jsx @@ -9,8 +9,11 @@ import { ChangeFolder } from "../../../../../../actions/filebrowser_actions"; import { useEffect } from "react"; import { getSubtreeFileCount } from "../../../../../../utils/folderUtils"; +import { useNavigate } from "react-router-dom"; + const Folder = ({ folder, state }) => { const dispatch = useDispatch(); + const navigate = useNavigate(); const _state = useSelector((state) => state.fileBrowser); const [open, setOpen] = useState(state ? state : false); const fileCount = getSubtreeFileCount(folder); @@ -23,6 +26,9 @@ const Folder = ({ folder, state }) => { const onClick = (folderData) => { dispatch(ChangeFolder(folderData)); setOpen(true); + if (_state.currentCourseCode && folderData?._id) { + navigate(`/browse/${_state.currentCourseCode}/${folderData._id}`); + } }; useEffect(() => { diff --git a/client/src/screens/browse/components/collapsible/index.jsx b/client/src/screens/browse/components/collapsible/index.jsx index 8a8a898..dc77e7b 100644 --- a/client/src/screens/browse/components/collapsible/index.jsx +++ b/client/src/screens/browse/components/collapsible/index.jsx @@ -36,6 +36,7 @@ const Collapsible = ({ course, color, state = false }) => { const allCourseData = useSelector((state) => state.fileBrowser.allCourseData); const dispatch = useDispatch(); const navigate = useNavigate(); + const currentFolder = useSelector((state) => state.fileBrowser.currentFolder); const { code, folderId } = useParams(); const isCurrentCourse = @@ -142,12 +143,12 @@ const Collapsible = ({ course, color, state = false }) => { useEffect(() => { if (!isCurrentCourse || !code || !folderId) return; if (code?.toLowerCase() !== normalizedCode) return; - const searchedFolder = searchFolderById(currentCourse, folderId); - if (searchedFolder) { + + if (searchedFolder && searchedFolder._id !== currentFolder?._id) { dispatch(ChangeFolder(searchedFolder)); } - }, [isCurrentCourse, code, folderId, normalizedCode, currentCourse, dispatch]); + }, [isCurrentCourse, code, folderId, normalizedCode, currentCourse, currentFolder, dispatch]); const showTree = !loading && diff --git a/client/src/screens/browse/index.jsx b/client/src/screens/browse/index.jsx index 72af548..b679a6c 100644 --- a/client/src/screens/browse/index.jsx +++ b/client/src/screens/browse/index.jsx @@ -67,7 +67,7 @@ const BrowseScreen = () => { }; if (folderData?.childType == "File" && folderData?.children?.length > 1) - folderData?.children.sort(sortFile); + folderData?.children.sort(sortFile); //sorting current folder files by name const contributionHandler = (event) => { const collection = document.getElementsByClassName("contri"); @@ -115,6 +115,18 @@ const BrowseScreen = () => { } }, []); + const findFolderById = (folders, id) => { + if (!folders || !Array.isArray(folders)) return null; + for (const folder of folders) { + if (folder._id === id) return folder; + if (folder.children?.length) { + const result = findFolderById(folder.children, id); + if (result) return result; + } + } + return null; + }; + useEffect(() => { if (loading || !code) { return; @@ -145,19 +157,15 @@ const BrowseScreen = () => { ) { const defaultYearIndex = fetchedData.children.length - 1; const defaultYear = fetchedData.children[defaultYearIndex]; + let activeFolder = folderId ? findFolderById(fetchedData.children, folderId) : null; + if (defaultYear && defaultYear.children) { dispatch( ChangeCurrentYearData(defaultYearIndex, defaultYear.children || []) ); dispatch(ClearFolderHistory()); // Clear history when starting with a new course/year - dispatch(ChangeFolder(defaultYear)); - refreshCourseFromServer(dispatch, code, { - yearIndex: defaultYearIndex, - folderId: folderId || defaultYear._id, - }); - } else { + dispatch(ChangeFolder(activeFolder || defaultYear)); } - } else { } } else { let fetchingToast = toast.loading("Loading course data..."); @@ -186,12 +194,14 @@ const BrowseScreen = () => { ) { const defaultYearIndex = fetchedData.children.length - 1; const defaultYear = fetchedData.children[defaultYearIndex]; + let activeFolder = folderId ? findFolderById(fetchedData.children, folderId) : null; + if (defaultYear && defaultYear.children) { dispatch( ChangeCurrentYearData(defaultYearIndex, defaultYear.children || []) ); dispatch(ClearFolderHistory()); // Clear history when starting with a new course/year - dispatch(ChangeFolder(defaultYear)); + dispatch(ChangeFolder(activeFolder || defaultYear)); } } } else { @@ -203,6 +213,40 @@ const BrowseScreen = () => { run(); }, [loading, code]); + useEffect(() => { + if (!code || !currCourse || !Array.isArray(currCourse) || currCourse.length === 0) { + return; + } + + if (folderId) { + if (folderData?._id === folderId) return; + + const matched = findFolderById(currCourse, folderId); + if (matched) { + dispatch(ChangeFolder(matched)); + } else { + fetchFolder(folderId, code) + .then((freshFolder) => { + if (freshFolder && freshFolder._id) { + dispatch(ChangeFolder(freshFolder)); + } else { + toast.error("Folder not found!"); + navigate(`/browse/${code}`, { replace: true }); + } + }) + .catch(() => { + toast.error("Folder not found!"); + navigate(`/browse/${code}`, { replace: true }); + }); + } + } else { + const defaultYear = currCourse[currYear !== null && currYear !== undefined ? currYear : currCourse.length - 1]; + if (defaultYear && folderData?._id !== defaultYear._id) { + dispatch(ChangeFolder(defaultYear)); + } + } + }, [folderId, code, currCourse, currYear, folderData]); + useEffect(() => { const refreshFolderData = async () => { if (!folderData?._id || !currCourseCode) return; @@ -223,40 +267,37 @@ const BrowseScreen = () => { refreshFolderData(); }, [refreshKey]); - const findFolderById = (folders, id) => { - for (const folder of folders) { - if (folder._id === id) return folder; - if (folder.children?.length) { - const result = findFolderById(folder.children, id); - if (result) return result; - } - } - return null; - }; - const HeaderText = folderData?.childType === "File" ? "Select a file..." : folderData?.childType === "Folder" - ? "Select a folder..." - : currCourse - ? "No data available for this course" - : "Select a course..."; + ? "Select a folder..." + : currCourse + ? "No data available for this course" + : "Select a course..."; + const handleBackClick = async () => { if (folderHistory.length > 0) { const previousFolder = folderHistory[folderHistory.length - 1]; dispatch(PopFolderHistory()); if (previousFolder && previousFolder._id) { - try { - const freshFolder = await fetchFolder(previousFolder._id, currCourseCode); - dispatch(ChangeFolder(freshFolder)); - } catch (err) { - // Fallback to popped history snapshot + dispatch(ChangeFolder(previousFolder)); + const isRootYear = currCourse.some(y => y._id === previousFolder._id); + if (isRootYear) { + navigate(`/browse/${currCourseCode}`); + } else { + navigate(`/browse/${currCourseCode}/${previousFolder._id}`); } + + } else { + navigate(`/browse/${currCourseCode}`); } + } else { + navigate(`/browse/${currCourseCode}`); } }; - const canGoBack = folderHistory.length > 0; + + const canGoBack = folderHistory.length > 0 || !!(folderId && folderData?._id); const allCourses = [ ...(user.user?.courses || []), ...(user.localCourses || []), @@ -270,7 +311,7 @@ const BrowseScreen = () => { try { dispatch(ChangeCurrentYearData(null, [])); dispatch(ChangeFolder(null)); - dispatch(ClearFolderHistory()); // Clear folder history when changing courses + dispatch(ClearFolderHistory()); let courseData = allCourseData?.find( (course) => hasUsableCourseTree(course) && @@ -326,6 +367,11 @@ const BrowseScreen = () => { dispatch(ChangeCurrentYearData(selectedYearIndex, selectedYear.children)); dispatch(ChangeFolder(selectedYear)); dispatch(RefreshCurrentFolder()); + if (selectedYear._id) { + navigate(`/browse/${currCourseCode}/${selectedYear._id}`); + } else { + navigate(`/browse/${currCourseCode}`); + } } } }; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 499a11a..0000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "CourseHub", - "lockfileVersion": 3, - "requires": true, - "packages": {} -}