CodeIDE / src /fileStore.js
FrederickSundeep's picture
commit initial 09-12-2025 016
5b4b486
// fileStore.js
const STORAGE_KEY = "devmate_ide_tree_v2";
/**
* Tree node structure:
* {
* type: "folder" | "file",
* name: "src" or "main.py",
* path: "src" or "src/main.py",
* children: [ ... ] // only for folders
* content: "..." // only for files
* }
*/
const defaultTree = {
type: "folder",
name: "root",
path: "",
children: [
{
type: "file",
name: "main.py",
path: "main.py",
content: "# Python\nprint('Hello from IDE')",
},
{
type: "folder",
name: "src",
path: "src",
children: [
{
type: "file",
name: "script.js",
path: "src/script.js",
content: "console.log('Hello from src');",
},
],
},
],
};
// ---------- persistence ----------
export function loadTree() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
return raw ? JSON.parse(raw) : defaultTree;
} catch (e) {
console.warn("loadTree failed:", e);
return defaultTree;
}
}
export function saveTree(tree) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tree));
} catch (e) {
console.warn("saveTree failed:", e);
}
}
// ---------- traversal helpers ----------
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
// find node and parent by path
export function findNodeAndParent(root, path) {
if (path === "" || path == null) return { node: root, parent: null };
const parts = path.split("/").filter(Boolean);
let node = root;
let parent = null;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
parent = node;
if (!parent.children) return { node: null, parent: null };
node = parent.children.find((c) => c.name === part);
if (!node) return { node: null, parent: null };
}
return { node, parent };
}
export function getNodeByPath(root, path) {
const r = findNodeAndParent(root, path);
return r.node || null;
}
// build child path
function joinPath(parentPath, name) {
if (!parentPath) return name;
if (!name) return parentPath;
return parentPath + "/" + name;
}
// ---------- CRUD operations ----------
// Add file under parentPath (string). If parentPath points to file, use its parent.
export function addFile(tree, filename, parentPath = "") {
const newTree = clone(tree);
// find parent
const { node: parent } = findNodeAndParent(newTree, parentPath);
const target = parent?.type === "folder" ? parent : newTree;
// ensure unique name
let name = filename;
const exists = (name) =>
target.children && target.children.some((c) => c.name === name);
let idx = 1;
const base = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
const ext = name.includes(".") ? name.slice(name.lastIndexOf(".")) : "";
while (exists(name)) {
name = `${base}_${idx++}${ext}`;
}
const path = joinPath(target.path, name);
const newNode = { type: "file", name, path, content: `// ${name}\n` };
target.children = target.children || [];
target.children.push(newNode);
return newTree;
}
// Add folder under parentPath
export function addFolder(tree, folderName, parentPath = "") {
const newTree = clone(tree);
const { node: parent } = findNodeAndParent(newTree, parentPath);
const target = parent?.type === "folder" ? parent : newTree;
let name = folderName;
const exists = (name) =>
target.children && target.children.some((c) => c.name === name && c.type === "folder");
let idx = 1;
while (exists(name)) {
name = `${folderName}_${idx++}`;
}
const path = joinPath(target.path, name);
const newNode = { type: "folder", name, path, children: [] };
target.children = target.children || [];
target.children.push(newNode);
return newTree;
}
// Delete node (file or folder) at given path (recursive for folders)
export function deleteNode(tree, path) {
const newTree = clone(tree);
if (!path) return newTree; // cannot delete root
const parts = path.split("/").filter(Boolean);
const nameToDelete = parts.pop();
const parentPath = parts.join("/");
const { node: parent } = findNodeAndParent(newTree, parentPath);
if (!parent || !parent.children) return newTree;
parent.children = parent.children.filter((c) => c.name !== nameToDelete);
return newTree;
}
// Rename node: change name and update paths recursively for children
export function renameNode(tree, path, newName) {
const newTree = clone(tree);
const { node, parent } = findNodeAndParent(newTree, path);
if (!node || !parent) {
// special case root rename not allowed
if (node && !parent) return newTree;
return newTree;
}
// ensure no duplicate under parent
if (parent.children.some((c) => c.name === newName && c.path !== node.path)) {
// collision: abort
return newTree;
}
const oldPath = node.path;
node.name = newName;
const newPath = joinPath(parent.path, newName);
// update path recursively
function updatePaths(n, currPath) {
n.path = currPath;
if (n.children) {
for (let child of n.children) {
const childNewPath = currPath ? currPath + "/" + child.name : child.name;
updatePaths(child, childNewPath);
}
}
}
updatePaths(node, newPath);
return newTree;
}
// Update file content
export function updateFileContent(tree, path, content) {
const newTree = clone(tree);
const { node } = findNodeAndParent(newTree, path);
if (node && node.type === "file") {
node.content = content;
}
return newTree;
}
// Search within files (returns array of {path, file, excerpt})
export function searchTree(node, term, results = []) {
if (!node) return results;
if (node.type === "file" && node.content && node.content.includes(term)) {
results.push({
path: node.path,
file: node.name,
excerpt: node.content
.split("\n")
.filter((l) => l.includes(term))
.slice(0, 5)
.join("\n"),
});
}
if (node.children) {
for (const c of node.children) searchTree(c, term, results);
}
return results;
}