Skip to content
Open
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
119 changes: 114 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^2.10.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1"
"react-icons": "^5.0.1",
"react-redux": "^9.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
Expand Down
31 changes: 4 additions & 27 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
import { useState } from "react";
import { useSelector } from "react-redux";
import "./App.css";
import folderData from "./data/folderData";
import Folder from "./components/Folder";
import useTraverseTree from "./hooks/use-traverse-tree";
import { selectRootId } from "./Repository/Slices/explorerSlice";

function App() {
const [explorerData, setExplorerData] = useState(folderData);
const { insertNode, deleteNode, updateNode } = useTraverseTree();
const handleInsertNode = (folderId, itemName, isFolder) => {
const finalItem = insertNode(explorerData, folderId, itemName, isFolder);
return finalItem;
};
const handleDeleteNode = (folderId) => {
// Call deleteNode to get the modified tree
const finalItem = deleteNode(explorerData, folderId);
// Update the explorerData state with the modified tree
setExplorerData(finalItem);
};

const handleUpdateFolder = (id, updatedValue, isFolder) => {
const finalItem = updateNode(explorerData, id, updatedValue, isFolder);
// Update the explorerData state with the modified tree
setExplorerData(finalItem);
};
const rootId = useSelector(selectRootId);

return (
<div className="App">
<div className="folderContainerBody">
<div className="folder-container">
<Folder
handleInsertNode={handleInsertNode}
handleDeleteNode={handleDeleteNode}
handleUpdateFolder={handleUpdateFolder}
explorerData={explorerData}
/>
{rootId ? <Folder nodeId={rootId} /> : null}
</div>
<div className="empty-state">Your content will be here</div>
</div>
Expand Down
102 changes: 102 additions & 0 deletions src/Repository/Slices/explorerSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { createSlice } from "@reduxjs/toolkit";
import folderData from "../../data/folderData";

const generateId = () =>
typeof crypto !== "undefined" && crypto.randomUUID
? crypto.randomUUID()
: String(Date.now());

function normalizeTree(node) {
const byId = {};
function walk(n) {
const id = n.id ?? generateId();
const children = Array.isArray(n.items) ? n.items : [];
const childIds = children.map((c) => {
const childId = walk(c);
return childId;
});
byId[id] = {
id,
name: n.name,
isFolder: !!n.isFolder,
childrenIds: childIds,
};
return id;
}
const rootId = walk(folderData); // seed from existing data
return { byId, rootId };
}

const initial = normalizeTree(folderData);

const explorerSlice = createSlice({
name: "explorer",
initialState: {
nodes: { byId: initial.byId },
rootId: initial.rootId,
},
reducers: {
insertNode: {
prepare: (parentId, name, isFolder) => ({
payload: { parentId, name: String(name).trim(), isFolder: !!isFolder },
}),
reducer: (state, action) => {
const { parentId, name, isFolder } = action.payload;
if (!name) return;
const parent = state.nodes.byId[parentId];
if (!parent || !parent.isFolder) return;

const id = generateId();
state.nodes.byId[id] = {
id,
name,
isFolder,
childrenIds: [],
};
parent.childrenIds.unshift(id);
},
},
renameNode(state, action) {
const { id, name } = action.payload;
const node = state.nodes.byId[id];
if (node && String(name).trim()) {
node.name = String(name).trim();
}
},
deleteNode(state, action) {
const { id } = action.payload;
if (!state.nodes.byId[id]) return;

// remove id from any parent list (O(n) but simple)
Object.values(state.nodes.byId).forEach((n) => {
if (n.childrenIds?.length) {
n.childrenIds = n.childrenIds.filter((cid) => cid !== id);
}
});

function removeRecursively(nodeId) {
const node = state.nodes.byId[nodeId];
if (!node) return;
if (node.childrenIds?.length) {
node.childrenIds.forEach(removeRecursively);
}
delete state.nodes.byId[nodeId];
}
// if deleting root, just clear everything but keep empty root
if (id === state.rootId) {
const root = state.nodes.byId[state.rootId];
root.childrenIds.forEach(removeRecursively);
root.childrenIds = [];
return;
}
removeRecursively(id);
},
},
});

export const { insertNode, renameNode, deleteNode } = explorerSlice.actions;

export const selectRootId = (state) => state.explorer.rootId;
export const selectNodeById = (state, id) => state.explorer.nodes.byId[id];

export default explorerSlice.reducer;
Loading