AdhyanshVerma's picture
download
raw
22.5 kB
**index.html**
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Virtual File Explorer</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="app">
<aside class="pane pane-tree" id="treePane">
<div class="pane-header">
<h2>Explorer</h2>
<input type="search" id="searchInput" placeholder="Search files & folders…" autocomplete="off" />
</div>
<div class="tree" id="tree" tabindex="0" role="tree" aria-label="File system tree"></div>
</aside>
<main class="pane pane-content" id="contentPane">
<div class="pane-header content-header">
<nav class="breadcrumbs" id="breadcrumbs" aria-label="Breadcrumb"></nav>
<div class="actions">
<button id="newFileBtn" class="btn">+ New File</button>
<button id="newFolderBtn" class="btn">+ New Folder</button>
</div>
</div>
<div class="content" id="content"></div>
</main>
</div>
<ul class="context-menu" id="contextMenu" role="menu" hidden>
<li role="menuitem" data-action="rename">Rename</li>
<li role="menuitem" data-action="delete">Delete</li>
</ul>
<script src="app.js"></script>
</body>
</html>
```
**styles.css**
```css
:root{
--bg:#0f1115;
--bg-2:#161922;
--bg-3:#1d212c;
--bg-hover:#232838;
--border:#2a3040;
--text:#e6e9ef;
--text-dim:#8b93a7;
--accent:#5b9dff;
--accent-2:#7c5cff;
--folder:#f5c043;
--file:#5b9dff;
--danger:#ff5d6c;
--radius:10px;
--shadow:0 10px 30px rgba(0,0,0,.35);
--mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
background:radial-gradient(1200px 600px at 80% -10%,rgba(124,92,255,.12),transparent 60%),
radial-gradient(900px 500px at -10% 110%,rgba(91,157,255,.10),transparent 60%),
var(--bg);
color:var(--text);
overflow:hidden;
}
.app{
display:grid;
grid-template-columns:320px 1fr;
height:100vh;
gap:1px;
background:var(--border);
}
.pane{
background:var(--bg-2);
display:flex;
flex-direction:column;
min-width:0;
min-height:0;
}
.pane-header{
padding:14px 16px;
border-bottom:1px solid var(--border);
display:flex;
flex-direction:column;
gap:10px;
background:linear-gradient(180deg,rgba(255,255,255,.02),transparent);
}
.pane-header h2{
margin:0;
font-size:13px;
letter-spacing:.12em;
text-transform:uppercase;
color:var(--text-dim);
}
.content-header{
flex-direction:row;
align-items:center;
justify-content:space-between;
gap:12px;
flex-wrap:wrap;
}
#searchInput{
width:100%;
padding:9px 12px;
background:var(--bg-3);
border:1px solid var(--border);
border-radius:8px;
color:var(--text);
font-size:13px;
outline:none;
transition:border-color .15s, box-shadow .15s;
}
#searchInput:focus{
border-color:var(--accent);
box-shadow:0 0 0 3px rgba(91,157,255,.18);
}
.tree{
overflow:auto;
padding:8px 6px;
font-size:13px;
line-height:1.9;
outline:none;
flex:1;
}
.tree-node{user-select:none}
.tree-row{
display:flex;
align-items:center;
gap:6px;
padding:4px 8px;
border-radius:6px;
cursor:pointer;
color:var(--text);
white-space:nowrap;
}
.tree-row:hover{background:var(--bg-hover)}
.tree-row.selected{background:rgba(91,157,255,.16);color:#fff}
.tree-row.kb-focus{
background:rgba(124,92,255,.22);
outline:1px solid rgba(124,92,255,.5);
outline-offset:-1px;
}
.tree-row.hidden{display:none}
.chevron{
width:14px;
display:inline-flex;
justify-content:center;
color:var(--text-dim);
transition:transform .15s ease;
font-size:10px;
}
.chevron.open{transform:rotate(90deg)}
.chevron.invisible{visibility:hidden}
.node-icon{font-size:14px;width:16px;text-align:center}
.icon-folder{color:var(--folder)}
.icon-file{color:var(--file)}
.tree-children{padding-left:14px;border-left:1px dashed var(--border);margin-left:10px}
.tree-children.collapsed{display:none}
.content{flex:1;overflow:auto;padding:20px 22px}
.breadcrumbs{display:flex;flex-wrap:wrap;align-items:center;gap:4px;font-size:13px;min-height:24px}
.crumb{
background:none;border:none;color:var(--text-dim);
cursor:pointer;padding:4px 8px;border-radius:6px;font-size:13px;
}
.crumb:hover{background:var(--bg-hover);color:var(--text)}
.crumb.current{color:var(--text);font-weight:600}
.crumb-sep{color:var(--text-dim);opacity:.5}
.actions{display:flex;gap:8px}
.btn{
background:var(--bg-3);
border:1px solid var(--border);
color:var(--text);
padding:7px 12px;
border-radius:8px;
font-size:12px;
cursor:pointer;
transition:all .15s;
}
.btn:hover{background:var(--bg-hover);border-color:var(--accent)}
.btn.primary{background:linear-gradient(135deg,var(--accent),var(--accent-2));border-color:transparent;color:#fff}
.grid{
display:grid;
grid-template-columns:repeat(auto-fill,minmax(110px,1fr));
gap:14px;
}
.grid-item{
display:flex;
flex-direction:column;
align-items:center;
gap:8px;
padding:16px 8px;
border-radius:var(--radius);
background:var(--bg-3);
border:1px solid transparent;
cursor:pointer;
text-align:center;
transition:transform .12s, border-color .15s, background .15s;
}
.grid-item:hover{
transform:translateY(-2px);
border-color:var(--accent);
background:var(--bg-hover);
}
.grid-icon{font-size:34px;line-height:1}
.grid-name{
font-size:12px;
word-break:break-word;
width:100%;
line-height:1.3;
}
.grid-meta{font-size:10px;color:var(--text-dim);font-family:var(--mono)}
.preview{
background:var(--bg-3);
border:1px solid var(--border);
border-radius:var(--radius);
padding:28px;
max-width:560px;
box-shadow:var(--shadow);
}
.preview h3{margin:0 0 4px;font-size:18px;word-break:break-word}
.preview .ext{color:var(--accent);font-family:var(--mono);font-size:13px}
.preview .meta-row{
display:flex;justify-content:space-between;
padding:10px 0;border-bottom:1px dashed var(--border);font-size:13px;
}
.preview .meta-row:last-of-type{border-bottom:none}
.preview .placeholder{
margin-top:18px;
border:2px dashed var(--border);
border-radius:8px;
padding:34px;
text-align:center;
color:var(--text-dim);
font-size:13px;
}
.preview .placeholder .big{font-size:42px;display:block;margin-bottom:8px}
.empty{color:var(--text-dim);font-size:13px;padding:30px;text-align:center}
.context-menu{
position:fixed;
z-index:1000;
min-width:160px;
background:var(--bg-3);
border:1px solid var(--border);
border-radius:8px;
box-shadow:var(--shadow);
padding:6px;
margin:0;
list-style:none;
font-size:13px;
}
.context-menu li{
padding:8px 12px;
border-radius:6px;
cursor:pointer;
}
.context-menu li:hover{background:var(--bg-hover)}
.context-menu li[data-action="delete"]:hover{background:rgba(255,93,108,.18);color:var(--danger)}
.toast{
position:fixed;
bottom:20px;left:50%;
transform:translateX(-50%);
background:var(--bg-3);
border:1px solid var(--border);
padding:10px 16px;border-radius:8px;
font-size:13px;box-shadow:var(--shadow);
animation:fade .25s ease;
}
@keyframes fade{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
@media (max-width:760px){
.app{grid-template-columns:1fr;grid-template-rows:45vh 1fr}
.tree{max-height:none}
}
```
**app.js**
```javascript
/* ---------- Data ---------- */
const fileSystem = {
name: "root", type: "folder", children: [
{ name: "Documents", type: "folder", children: [
{ name: "Resume.pdf", type: "file", extension: "pdf", size: 245678 },
{ name: "CoverLetter.docx", type: "file", extension: "docx", size: 88120 },
{ name: "Notes", type: "folder", children: [
{ name: "ideas.txt", type: "file", extension: "txt", size: 2048 },
{ name: "todo.md", type: "file", extension: "md", size: 1536 }
]}
]},
{ name: "Pictures", type: "folder", children: [
{ name: "vacation.jpg", type: "file", extension: "jpg", size: 1843200 },
{ name: "sunset.png", type: "file", extension: "png", size: 982432 },
{ name: "Screenshots", type: "folder", children: [
{ name: "shot-2024.png", type: "file", extension: "png", size: 440000 }
]}
]},
{ name: "Music", type: "folder", children: [
{ name: "track01.mp3", type: "file", extension: "mp3", size: 5242880 },
{ name: "track02.mp3", type: "file", extension: "mp3", size: 4980736 }
]},
{ name: "Projects", type: "folder", children: [
{ name: "webapp", type: "folder", children: [
{ name: "index.html", type: "file", extension: "html", size: 4096 },
{ name: "styles.css", type: "file", extension: "css", size: 6144 },
{ name: "app.js", type: "file", extension: "js", size: 12288 }
]},
{ name: "README.md", type: "file", extension: "md", size: 1024 }
]},
{ name: "config.json", type: "file", extension: "json", size: 512 }
]
};
/* ---------- State ---------- */
let nextId = 1;
function assignIds(node, parent=null){
node.__id = nextId++;
node.__parent = parent;
if (node.children) node.children.forEach(c => assignIds(c, node));
}
assignIds(fileSystem);
const state = {
expanded: new Set([fileSystem.__id]),
selectedId: fileSystem.__id, // currently viewed folder
kbFocusId: null, // keyboard-focused tree node
previewNode: null, // file being previewed
query: ""
};
const byId = id => {
let found = null;
(function walk(n){ if(n.__id===id){found=n;return} if(n.children) n.children.forEach(walk); })(fileSystem);
return found;
};
/* ---------- Helpers ---------- */
function formatSize(b){
if (b == null) return "—";
const u=["B","KB","MB","GB"]; let i=0;
while(b>=1024 && i<u.length-1){b/=1024;i++}
return b.toFixed(b<10 && i>0?1:0)+" "+u[i];
}
function iconFor(node){
if (node.type==="folder") return "📁";
const e=node.extension||"";
const m={
pdf:"📄",docx:"📝",doc:"📝",txt:"📃",md:"📑",
jpg:"🖼️",jpeg:"🖼️",png:"🖼️",gif:"🖼️",svg:"🖼️",
mp3:"🎵",wav:"🎵",flac:"🎵",
html:"🌐",css:"🎨",js:"⚙️",json:"🔧",zip:"🗜️"
};
return m[e]||"📄";
}
function pathTo(node){
const p=[]; let n=node;
while(n){ p.unshift(n); n=n.__parent; }
return p;
}
function isFolder(n){ return n.type==="folder"; }
/* ---------- Tree rendering ---------- */
const treeEl = document.getElementById("tree");
function nodeMatches(node, q){
return node.name.toLowerCase().includes(q);
}
function subtreeHasMatch(node, q){
if (nodeMatches(node,q)) return true;
if (node.children) return node.children.some(c=>subtreeHasMatch(c,q));
return false;
}
function renderTree(){
treeEl.innerHTML="";
renderNode(fileSystem, treeEl, 0);
}
function renderNode(node, container, depth){
const q = state.query.trim().toLowerCase();
if (q){
if (!subtreeHasMatch(node, q)) return;
}
const wrap = document.createElement("div");
wrap.className = "tree-node";
wrap.dataset.id = node.__id;
const row = document.createElement("div");
row.className = "tree-row";
row.dataset.id = node.__id;
row.tabIndex = -1;
if (state.selectedId === node.__id && isFolder(node)) row.classList.add("selected");
if (state.kbFocusId === node.__id) row.classList.add("kb-focus");
const chev = document.createElement("span");
chev.className = "chevron";
if (isFolder(node) && node.children && node.children.length){
chev.textContent = "▶";
if (state.expanded.has(node.__id)) chev.classList.add("open");
} else {
chev.classList.add("invisible");
}
const ic = document.createElement("span");
ic.className = "node-icon " + (isFolder(node)?"icon-folder":"icon-file");
ic.textContent = iconFor(node);
const nm = document.createElement("span");
nm.className = "node-name";
nm.textContent = node.name;
row.appendChild(chev); row.appendChild(ic); row.appendChild(nm);
wrap.appendChild(row);
// events
row.addEventListener("click", e=>{
e.stopPropagation();
if (isFolder(node)){
toggleExpand(node);
selectFolder(node);
} else {
selectFile(node);
}
});
row.addEventListener("contextmenu", e=>{
e.preventDefault();
showContextMenu(e.clientX, e.clientY, node);
});
if (isFolder(node) && node.children){
const childWrap = document.createElement("div");
childWrap.className = "tree-children";
if (!state.expanded.has(node.__id)) childWrap.classList.add("collapsed");
node.children.forEach(c => renderNode(c, childWrap, depth+1));
wrap.appendChild(childWrap);
}
container.appendChild(wrap);
}
function toggleExpand(node){
if (!isFolder(node)) return;
if (state.expanded.has(node.__id)) state.expanded.delete(node.__id);
else state.expanded.add(node.__id);
renderTree();
}
/* ---------- Selection ---------- */
function selectFolder(node){
state.selectedId = node.__id;
state.previewNode = null;
state.kbFocusId = node.__id;
renderTree();
renderContent();
}
function selectFile(node){
state.previewNode = node;
state.kbFocusId = node.__id;
renderTree();
renderContent();
}
/* ---------- Content / breadcrumb / grid / preview ---------- */
const contentEl = document.getElementById("content");
const crumbsEl = document.getElementById("breadcrumbs");
function renderContent(){
// breadcrumbs
crumbsEl.innerHTML="";
const target = state.previewNode || byId(state.selectedId) || fileSystem;
const path = pathTo(target);
path.forEach((n, i)=>{
const b = document.createElement("button");
b.className = "crumb" + (i===path.length-1?" current":"");
b.textContent = n.name;
b.addEventListener("click", ()=>{
if (isFolder(n)) selectFolder(n);
});
crumbsEl.appendChild(b);
if (i < path.length-1){
const sep = document.createElement("span");
sep.className="crumb-sep"; sep.textContent="/";
crumbsEl.appendChild(sep);
}
});
contentEl.innerHTML="";
if (state.previewNode && state.previewNode.type==="file"){
renderFilePreview(state.previewNode);
return;
}
const folder = byId(state.selectedId) || fileSystem;
if (!folder.children || !folder.children.length){
const e=document.createElement("div");
e.className="empty";
e.textContent="This folder is empty.";
contentEl.appendChild(e);
return;
}
const grid = document.createElement("div");
grid.className="grid";
folder.children.forEach(child=>{
const item = document.createElement("div");
item.className="grid-item";
item.dataset.id = child.__id;
const ic = document.createElement("div");
ic.className="grid-icon";
ic.textContent = iconFor(child);
const nm = document.createElement("div");
nm.className="grid-name";
nm.textContent = child.name;
const mt = document.createElement("div");
mt.className="grid-meta";
mt.textContent = isFolder(child) ? "Folder" : formatSize(child.size);
item.appendChild(ic); item.appendChild(nm); item.appendChild(mt);
item.addEventListener("click", ()=>{
if (isFolder(child)){
state.expanded.add(child.__id);
selectFolder(child);
} else {
selectFile(child);
}
});
item.addEventListener("contextmenu", e=>{
e.preventDefault();
showContextMenu(e.clientX, e.clientY, child);
});
grid.appendChild(item);
});
contentEl.appendChild(grid);
}
function renderFilePreview(node){
const wrap = document.createElement("div");
wrap.className="preview";
const h = document.createElement("h3");
h.textContent = node.name;
const ext = document.createElement("div");
ext.className="ext";
ext.textContent = node.extension ? "."+node.extension : "no extension";
wrap.appendChild(h); wrap.appendChild(ext);
const rows = [
["Type","File"],
["Extension", node.extension || "—"],
["Size", formatSize(node.size)],
["Path", pathTo(node).map(n=>n.name).join(" / ")]
];
rows.forEach(([k,v])=>{
const r=document.createElement("div");
r.className="meta-row";
const k2=document.createElement("span"); k2.textContent=k; k2.style.color="var(--text-dim)";
const v2=document.createElement("span"); v2.textContent=v; v2.style.fontFamily="var(--mono)";
r.appendChild(k2); r.appendChild(v2);
wrap.appendChild(r);
});
const ph=document.createElement("div");
ph.className="placeholder";
ph.innerHTML = `<span class="big">${iconFor(node)}</span>No inline preview available for <strong>.${node.extension||"file"}</strong> files.`;
wrap.appendChild(ph);
contentEl.appendChild(wrap);
}
/* ---------- Search ---------- */
const searchInput = document.getElementById("searchInput");
searchInput.addEventListener("input", e=>{
state.query = e.target.value;
if (state.query.trim()){
// auto-expand parents of matches
(function expand(n){
if (subtreeHasMatch(n, state.query.trim().toLowerCase()) && isFolder(n)){
state.expanded.add(n.__id);
if (n.children) n.children.forEach(expand);
}
})(fileSystem);
}
renderTree();
});
/* ---------- Keyboard navigation ---------- */
function visibleTreeNodes(){
const out=[];
function walk(node, parentExpanded){
if (!parentExpanded) return;
const q = state.query.trim().toLowerCase();
if (q && !subtreeHasMatch(node,q)) return;
out.push(node);
if (isFolder(node) && node.children && state.expanded.has(node.__id)){
node.children.forEach(c=>walk(c, true));
}
}
walk(fileSystem, true);
return out;
}
treeEl.addEventListener("keydown", e=>{
const visible = visibleTreeNodes();
if (!visible.length) return;
let idx = visible.findIndex(n=>n.__id===state.kbFocusId);
if (idx === -1) idx = 0;
if (e.key === "ArrowDown"){
e.preventDefault();
idx = Math.min(visible.length-1, idx+1);
state.kbFocusId = visible[idx].__id;
renderTree(); scrollKbIntoView();
} else if (e.key === "ArrowUp"){
e.preventDefault();
idx = Math.max(0, idx-1);
state.kbFocusId = visible[idx].__id;
renderTree(); scrollKbIntoView();
} else if (e.key === "ArrowRight"){
e.preventDefault();
const n = visible[idx];
if (isFolder(n) && !state.expanded.has(n.__id)){ state.expanded.add(n.__id); renderTree(); }
} else if (e.key === "ArrowLeft"){
e.preventDefault();
const n = visible[idx];
if (isFolder(n) && state.expanded.has(n.__id)){ state.expanded.delete(n.__id); renderTree(); }
else if (n.__parent){ state.kbFocusId = n.__parent.__id; renderTree(); scrollKbIntoView(); }
} else if (e.key === "Enter"){
e.preventDefault();
const n = visible[idx];
if (isFolder(n)) selectFolder(n);
else selectFile(n);
}
});
function scrollKbIntoView(){
const el = treeEl.querySelector(".kb-focus");
if (el) el.scrollIntoView({block:"nearest"});
}
/* ---------- Context menu ---------- */
const ctxMenu = document.getElementById("contextMenu");
let ctxTarget = null;
function showContextMenu(x,y,node){
ctxTarget = node;
ctxMenu.hidden = false;
ctxMenu.style.left = x+"px";
ctxMenu.style.top = y+"px";
// keep in viewport
const r = ctxMenu.getBoundingClientRect();
if (r.right > innerWidth) ctxMenu.style.left = (innerWidth - r.width - 8)+"px";
if (r.bottom > innerHeight) ctxMenu.style.top = (innerHeight - r.height - 8)+"px";
}
function hideContextMenu(){ ctxMenu.hidden = true; ctxTarget = null; }
document.addEventListener("click", hideContextMenu);
document.addEventListener("contextmenu", e=>{
if (!e.target.closest(".tree-row") && !e.target.closest(".grid-item")) hideContextMenu();
});
ctxMenu.addEventListener("click", e=>{
const li = e.target.closest("li[data-action]");
if (!li || !ctxTarget) return;
const action = li.dataset.action;
const node = ctxTarget;
hideContextMenu();
if (action==="rename") doRename(node);
else if (action==="delete") doDelete(node);
});
function doRename(node){
const v = prompt("Rename to:", node.name);
if (v && v.trim()){
node.name = v.trim();
renderTree(); renderContent();
toast(`Renamed to "${node.name}"`);
}
}
function doDelete(node){
if (!confirm(`Delete "${node.name}"?`)) return;
const parent = node.__parent;
if (!parent || !parent.children) return;
parent.children = parent.children.filter(c=>c.__id!==node.__id);
if (state.selectedId === node.__id) state.selectedId = parent.__id;
if (state.previewNode && state.previewNode.__id === node.__id) state.previewNode = null;
if (state.kbFocusId === node.__id) state.kbFocusId = parent.__id;
renderTree(); renderContent();
toast(`Deleted "${node.name}"`);
}
/* ---------- Create new ---------- */
document.getElementById("newFileBtn").addEventListener("click", ()=>{
const folder = byId(state.selectedId) || fileSystem;
if (!isFolder(folder)){ toast("Select a folder first."); return; }
const name = prompt("New file name (with extension):", "untitled.txt");
if (!name || !name.trim()) return;
const parts = name.trim().split(".");
const ext = parts.length>1 ? parts.pop() : "";
const node = { name: parts.join("."), type:"file", extension:ext, size:0 };
node.__parent = folder;
node.__id = nextId++;
folder.children = folder.children || [];
folder.children.push(node);
state.expanded.add(folder.__id);
renderTree(); renderContent();
toast(`Created file "${name}"`);
});
document.getElementById("newFolderBtn").addEventListener("click", ()=>{
const folder = byId(state.selectedId) || fileSystem;
if (!isFolder(folder)){ toast("Select a folder first."); return; }
const name = prompt("New folder name:", "New Folder");
if (!name || !name.trim()) return;
const node = { name: name.trim(), type:"folder", children:[] };
node.__parent = folder;
node.__id = nextId++;
folder.children = folder.children || [];
folder.children.push(node);
state.expanded.add(folder.__id);
renderTree(); renderContent();
toast(`Created folder "${name}"`);
});
/* ---------- Toast ---------- */
let toastTimer;
function toast(msg){
document.querySelectorAll(".toast").forEach(t=>t.remove());
const t = document.createElement("div");
t.className="toast"; t.textContent=msg;
document.body.appendChild(t);
clearTimeout(toastTimer);
toastTimer = setTimeout(()=>t.remove(), 2200);
}
/* ---------- Init ---------- */
renderTree();
renderContent();
treeEl.focus();
```

Xet Storage Details

Size:
22.5 kB
·
Xet hash:
900992f563101d6577a24d271d0fcdd1aa101ad34994ecfdd7d2c767d81f27e6

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.