/proc/4/root/var/task/_ssr
This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.
import { i as __toESM } from "../_runtime.mjs";import { P as require_react, g as require_jsx_runtime, h as useRouter, m as Link } from "../_libs/@tanstack/react-router+[...].mjs";import { n as createServerFn } from "./ssr.mjs";import { C as Check, S as ChevronRight, T as ArrowLeft, _ as FileImage, a as RotateCw, b as CornerLeftUp, c as Info, d as Folder, f as FolderOpen, g as FileLock2, h as FileTerminal, i as Save, l as Hash, m as FileText, n as SquareTerminal, o as Pencil, p as FileType, r as Search, s as Lock, t as X, u as HardDrive, v as FileCode, w as Braces, x as Copy, y as FileCog } from "../_libs/lucide-react.mjs";import { n as createSsrRpc, t as Route } from "./routes-CPlVcNZL.mjs";//#region node_modules/.nitro/vite/services/ssr/assets/routes-BaK3zTFM.jsvar import_react = /* @__PURE__ */ __toESM(require_react());var import_jsx_runtime = require_jsx_runtime();var runCommand = createServerFn({ method: "POST" }).validator((d) => d).handler(createSsrRpc("43655b12b2525448f49cc143e8e0a1f9d366816530f47cde443331fced610278"));var saveFile = createServerFn({ method: "POST" }).validator((d) => d).handler(createSsrRpc("3e9ad7efb5ff1b6ad57a93cc9f3882a022f3d539053d163dfd6fb95a332c1f69"));var MAP = { js: FileCode, jsx: FileCode, mjs: FileCode, cjs: FileCode, ts: FileCode, tsx: FileCode, mts: FileCode, cts: FileCode, py: FileCode, rb: FileCode, go: FileCode, rs: FileCode, java: FileCode, kt: FileCode, c: FileCode, h: FileCode, cpp: FileCode, cc: FileCode, hpp: FileCode, cs: FileCode, php: FileCode, swift: FileCode, lua: FileCode, dart: FileCode, vue: FileCode, svelte: FileCode, astro: FileCode, html: FileCode, htm: FileCode, css: FileCode, scss: FileCode, sass: FileCode, less: FileCode, xml: FileCode, sql: FileCode, graphql: FileCode, gql: FileCode, ex: FileCode, exs: FileCode, scala: FileCode, json: Braces, jsonc: Braces, yml: Braces, yaml: Braces, toml: Braces, csv: FileType, tsv: FileType, sh: FileTerminal, bash: FileTerminal, zsh: FileTerminal, fish: FileTerminal, env: FileCog, ini: FileCog, cfg: FileCog, conf: FileCog, cnf: FileCog, properties: FileCog, gitignore: FileCog, editorconfig: FileCog, dockerfile: FileCog, makefile: FileCog, service: FileCog, rules: FileCog, pem: FileLock2, crt: FileLock2, key: FileLock2, pub: FileLock2, md: FileText, markdown: FileText, mdx: FileText, txt: FileText, log: FileText, lock: FileText, list: FileText, png: FileImage, jpg: FileImage, jpeg: FileImage, gif: FileImage, webp: FileImage, svg: FileImage, ico: FileImage, bmp: FileImage, avif: FileImage};function FileGlyph({ type, ext, className }) { if (type === "dir") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Folder, { className, strokeWidth: 1.6 }); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MAP[ext] ?? (ext ? Hash : FileText), { className, strokeWidth: 1.6 });}var CODE_RULES = [ { type: "comment", re: /^\/\*[\s\S]*?\*\// }, { type: "comment", re: /^\/\/[^\n]*/ }, { type: "comment", re: /^#[^\n]*/ }, { type: "string", re: /^`(?:\\.|[^`\\])*`/ }, { type: "string", re: /^"(?:\\.|[^"\\])*"/ }, { type: "string", re: /^'(?:\\.|[^'\\])*'/ }, { type: "number", re: /^\b\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?\b/ }, { type: "keyword", re: new RegExp(`\\b(${[ "const", "let", "var", "function", "return", "if", "else", "for", "while", "do", "switch", "case", "break", "continue", "new", "class", "extends", "import", "export", "from", "default", "async", "await", "yield", "try", "catch", "finally", "throw", "typeof", "instanceof", "in", "of", "void", "delete", "this", "super", "static", "get", "set", "public", "private", "protected", "readonly", "interface", "type", "enum", "namespace", "implements", "as", "is", "keyof", "true", "false", "null", "undefined", "def", "elif", "lambda", "pass", "with", "and", "or", "not", "None", "True", "False", "self", "fn", "let", "mut", "pub", "struct", "impl", "match", "use", "mod", "package", "func", "go", "defer", "chan", "map", "string", "int", "bool", "float", "double", "char", "long", "short", "select", "insert", "update", "delete", "where", "join", "values" ].join("|")})\\b`) }, { type: "func", re: /^[A-Za-z_$][\w$]*(?=\s*\()/ }, { type: "punct", re: /^[{}()[\];,.:?=<>+\-*/%&|!~^]+/ }];function tokenizeCode(src) { const out = []; let i = 0; let buf = ""; const flush = () => { if (buf) { out.push({ type: "plain", value: buf }); buf = ""; } }; while (i < src.length) { const rest = src.slice(i); let matched = false; for (const rule of CODE_RULES) { const m = rule.re.exec(rest); if (m && m.index === 0 && m[0].length > 0) { flush(); out.push({ type: rule.type, value: m[0] }); i += m[0].length; matched = true; break; } } if (!matched) { buf += src[i]; i++; } } flush(); return out;}function tokenizeMarkup(src) { const out = []; const re = /(<!--[\s\S]*?-->)|(<\/?[A-Za-z][\w:-]*)|([A-Za-z_:][\w:.-]*)(?==)|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')|(>)/g; let last = 0; let m; while (m = re.exec(src)) { if (m.index > last) out.push({ type: "plain", value: src.slice(last, m.index) }); if (m[1]) out.push({ type: "comment", value: m[1] }); else if (m[2]) out.push({ type: "tag", value: m[2] }); else if (m[3]) out.push({ type: "attr", value: m[3] }); else if (m[4]) out.push({ type: "string", value: m[4] }); else if (m[5]) out.push({ type: "tag", value: m[5] }); last = re.lastIndex; } if (last < src.length) out.push({ type: "plain", value: src.slice(last) }); return out;}function tokenizeJson(src) { const out = []; const re = /("(?:\\.|[^"\\])*")(\s*:)?|(\b-?\d[\d.eE+-]*\b)|(\btrue\b|\bfalse\b|\bnull\b)|([{}[\],:])/g; let last = 0; let m; while (m = re.exec(src)) { if (m.index > last) out.push({ type: "plain", value: src.slice(last, m.index) }); if (m[1]) { out.push({ type: m[2] ? "key" : "string", value: m[1] }); if (m[2]) out.push({ type: "punct", value: m[2] }); } else if (m[3]) out.push({ type: "number", value: m[3] }); else if (m[4]) out.push({ type: "keyword", value: m[4] }); else if (m[5]) out.push({ type: "punct", value: m[5] }); last = re.lastIndex; } if (last < src.length) out.push({ type: "plain", value: src.slice(last) }); return out;}var COLOR = { plain: "text-[#c9d3e3]", comment: "text-[#5b6577] italic", string: "text-[#9ece9a]", number: "text-[#d4af37]", keyword: "text-[#8b95f6]", func: "text-[#7fb4e8]", punct: "text-[#7c879b]", tag: "text-[#8b95f6]", attr: "text-[#d4af37]", key: "text-[#7fb4e8]"};var MARKUP_EXTS = new Set([ "html", "htm", "xml", "svg", "vue", "svelte", "astro"]);var JSON_EXTS = new Set(["json", "jsonc"]);var PLAIN_EXTS = new Set([ "txt", "log", "csv", "tsv", "md", "markdown", "mdx", "env"]);function highlight(code, ext) { if (PLAIN_EXTS.has(ext)) return code; let tokens; if (JSON_EXTS.has(ext)) tokens = tokenizeJson(code); else if (MARKUP_EXTS.has(ext)) tokens = tokenizeMarkup(code); else tokens = tokenizeCode(code); return tokens.map((t, idx) => t.type === "plain" ? t.value : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: COLOR[t.type], children: t.value }, idx));}function formatBytes(n) { if (n <= 0) return "0 B"; const units = [ "B", "KB", "MB", "GB", "TB" ]; const i = Math.min(Math.floor(Math.log(n) / Math.log(1024)), units.length - 1); const v = n / Math.pow(1024, i); return `${i === 0 ? String(n) : v.toFixed(v >= 100 || v === Math.floor(v) ? 0 : 1)} ${units[i]}`;}function joinRel(dir, name) { return dir ? `${dir}/${name}` : name;}function parentRel(relPath) { if (!relPath) return ""; const parts = relPath.split("/").filter(Boolean); parts.pop(); return parts.join("/");}function cn(...inputs) { return inputs.filter(Boolean).join(" ");}function Explorer() { const { list, file, env } = Route.useLoaderData(); const router = useRouter(); const [query, setQuery] = (0, import_react.useState)(""); const [termOpen, setTermOpen] = (0, import_react.useState)(false); const [noticeDismissed, setNoticeDismissed] = (0, import_react.useState)(false); (0, import_react.useEffect)(() => { setQuery(""); }, [list.relPath]); const filtered = (0, import_react.useMemo)(() => { const q = query.trim().toLowerCase(); if (!q) return list.entries; return list.entries.filter((e) => e.name.toLowerCase().includes(q)); }, [list.entries, query]); const atRoot = list.relPath === ""; const dirCount = list.entries.filter((e) => e.type === "dir").length; const fileCount = list.entries.length - dirCount; return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mx-auto flex min-h-dvh w-full max-w-3xl flex-col px-4 pb-[max(5.5rem,calc(env(safe-area-inset-bottom)+5rem))] pt-[max(1.25rem,env(safe-area-inset-top))] sm:px-6", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Header, { relPath: list.relPath, onRefresh: () => router.invalidate() }), !env.workspaceUser && !noticeDismissed && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-3 flex items-start gap-2.5 rounded-xl border border-[var(--gold)]/25 bg-[var(--gold-soft)] px-3.5 py-3", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Info, { className: "mt-0.5 size-4 shrink-0 text-[var(--gold)]", "aria-hidden": "true" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-[13px] leading-snug text-[var(--ink)]", children: [ "This explorer reads the filesystem of the server it runs on, so", " ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-mono text-[12px]", children: "/workspace/user" }), " isn't present here. Browsing and the terminal still work against this server's own disk from", " ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-mono text-[12px]", children: "/" }), "." ] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => setNoticeDismissed(true), "aria-label": "Dismiss notice", className: "grid size-6 shrink-0 place-items-center rounded-lg text-[var(--gold)]/80 transition-colors hover:bg-white/5 hover:text-[var(--gold)] active:scale-[0.95]", style: { touchAction: "manipulation" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3.5" }) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Breadcrumbs, { segments: list.segments, atRoot }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toolbar, { atRoot, parentPath: parentRel(list.relPath), dirCount, fileCount, query, onQuery: setQuery }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-3 flex-1", children: list.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Notice, { icon: list.error === "Permission denied" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Lock, { className: "size-5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-5" }), title: list.error, body: list.error === "Permission denied" ? "This location can't be read in the current environment." : "This path could not be opened." }) : filtered.length === 0 ? query ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Notice, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Search, { className: "size-5" }), title: "No matches", body: `Nothing here matches "${query}".` }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Notice, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderOpen, { className: "size-5" }), title: "Empty folder", body: "There's nothing inside this directory." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "overflow-hidden rounded-2xl border border-[var(--edge)] bg-[var(--panel)]", children: filtered.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EntryRow, { entry, dir: list.relPath, first: i === 0 }, entry.name)) }) }), file ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Viewer, { file, dirPath: list.relPath }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", onClick: () => setTermOpen(true), className: "fixed bottom-[max(1rem,env(safe-area-inset-bottom))] left-1/2 z-30 flex -translate-x-1/2 items-center gap-2 rounded-full border border-[var(--edge-strong)] bg-[var(--bg-bottom)]/95 px-4 py-2.5 text-[13px] font-medium text-[var(--ink)] shadow-[0_10px_30px_-8px_rgba(0,0,0,0.8)] backdrop-blur transition-colors hover:border-[var(--indigo)]/50 hover:bg-[var(--panel-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SquareTerminal, { className: "size-4 text-[var(--indigo)]" }), "Terminal"] }), termOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Terminal, { cwd: list.relPath, onClose: () => setTermOpen(false) }) : null ] });}function Header({ relPath, onRefresh }) { const here = relPath ? "/" + relPath : "/"; return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "flex items-start justify-between gap-4", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "grid size-6 place-items-center rounded-md bg-[var(--indigo-soft)] text-[var(--indigo)]", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HardDrive, { className: "size-3.5", strokeWidth: 2 }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-[15px] font-medium tracking-tight text-[var(--ink)]", children: "File Explorer" })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "truncate font-mono text-[11px] text-[var(--muted)]", title: here, dir: "rtl", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("bdi", { children: here }) })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: onRefresh, "aria-label": "Refresh this folder", className: "grid size-9 shrink-0 place-items-center rounded-xl border border-[var(--edge)] bg-[var(--panel)] text-[var(--muted)] shadow-xs transition-colors hover:border-[var(--edge-strong)] hover:bg-[var(--panel-hover)] hover:text-[var(--ink)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RotateCw, { className: "size-4" }) })] });}function Breadcrumbs({ segments, atRoot }) { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("nav", { "aria-label": "Breadcrumb", className: "-mx-1 mt-4 flex items-center gap-1 overflow-x-auto px-1 py-1", style: { scrollbarWidth: "none" }, children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Crumb, { to: "", label: "root", isRoot: true, active: atRoot }), segments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex shrink-0 items-center gap-1", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "size-3.5 shrink-0 text-[var(--muted)]/60", "aria-hidden": "true" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Crumb, { to: seg.relPath, label: seg.name, active: i === segments.length - 1 })] }, seg.relPath))] });}function Crumb({ to, label, active, isRoot }) { return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/", search: to ? { path: to } : {}, className: cn("shrink-0 rounded-lg px-2 py-1 text-[12.5px] font-medium transition-colors active:scale-[0.97]", isRoot && "font-mono", active ? "bg-[var(--gold-soft)] text-[var(--gold)]" : "text-[var(--muted)] hover:bg-[var(--panel-hover)] hover:text-[var(--ink)]"), style: { touchAction: "manipulation" }, children: isRoot ? "/" : label });}function Toolbar({ atRoot, parentPath, dirCount, fileCount, query, onQuery }) { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-3 flex items-center gap-2", children: [ atRoot ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "grid size-10 shrink-0 place-items-center rounded-xl border border-[var(--edge)]/60 text-[var(--muted)]/40", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CornerLeftUp, { className: "size-4", "aria-hidden": "true" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/", search: parentPath ? { path: parentPath } : {}, "aria-label": "Go up one folder", className: "grid size-10 shrink-0 place-items-center rounded-xl border border-[var(--edge)] bg-[var(--panel)] text-[var(--ink)] shadow-xs transition-colors hover:border-[var(--edge-strong)] hover:bg-[var(--panel-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, { className: "size-4" }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Search, { className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[var(--muted)]", "aria-hidden": "true" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "text", inputMode: "search", value: query, onChange: (e) => onQuery(e.target.value), placeholder: "Filter this folder…", "aria-label": "Filter items in this folder", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, className: "h-10 w-full rounded-xl border border-[var(--edge)] bg-[var(--panel)] pl-9 pr-9 text-[15px] text-[var(--ink)] shadow-xs outline-none transition-colors placeholder:text-[var(--muted)] focus:border-[var(--indigo)]/60 focus:bg-[var(--panel-hover)]", style: { touchAction: "manipulation" } }), query && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => onQuery(""), "aria-label": "Clear filter", className: "absolute right-1.5 top-1/2 grid size-7 -translate-y-1/2 place-items-center rounded-lg text-[var(--muted)] transition-colors hover:bg-[var(--panel-hover)] hover:text-[var(--ink)] active:scale-[0.97]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-4" }) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "nums shrink-0 whitespace-nowrap text-right text-[11px] leading-tight text-[var(--muted)]", children: [ dirCount, " dir", dirCount === 1 ? "" : "s", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}), fileCount, " file", fileCount === 1 ? "" : "s" ] }) ] });}function EntryRow({ entry, dir, first }) { const childRel = joinRel(dir, entry.name); const isDir = entry.type === "dir"; const search = isDir ? { path: childRel } : { path: dir || void 0, file: childRel }; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: cn(!first && "border-t border-[var(--line)]"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, { to: "/", search, className: "group flex items-center gap-3 px-3.5 py-3 transition-colors hover:bg-[var(--panel-hover)] focus-visible:bg-[var(--panel-hover)] focus-visible:outline-none active:scale-[0.995]", style: { touchAction: "manipulation" }, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileGlyph, { type: entry.type, ext: entry.ext, className: cn("size-[18px] shrink-0", isDir ? "text-[var(--indigo)]" : "text-[var(--muted)]") }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "min-w-0 flex-1 truncate text-[15px] text-[var(--ink)]", children: entry.name }), isDir ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "size-4 shrink-0 text-[var(--muted)]/60 transition-transform group-hover:translate-x-0.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "nums shrink-0 text-[11px] text-[var(--muted)]", children: formatBytes(entry.size) }) ] }) });}function Notice({ icon, title, body }) { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center gap-3 rounded-2xl border border-dashed border-[var(--edge)] px-6 py-14 text-center", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "grid size-11 place-items-center rounded-xl bg-[var(--panel)] text-[var(--muted)]", children: icon }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[15px] font-medium text-[var(--ink)]", children: title }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[13px] text-[var(--muted)]", children: body })] })] });}function Viewer({ file, dirPath }) { const router = useRouter(); const [copied, setCopied] = (0, import_react.useState)(false); const [editing, setEditing] = (0, import_react.useState)(false); const [draft, setDraft] = (0, import_react.useState)(file.content ?? ""); const [saving, setSaving] = (0, import_react.useState)(false); const [saveMsg, setSaveMsg] = (0, import_react.useState)(null); const closeSearch = dirPath ? { path: dirPath } : {}; (0, import_react.useEffect)(() => { setCopied(false); setEditing(false); setDraft(file.content ?? ""); setSaveMsg(null); }, [file.relPath, file.content]); (0, import_react.useEffect)(() => { const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; }, []); (0, import_react.useEffect)(() => { const onKey = (e) => { if (e.key === "Escape" && !editing) router.navigate({ to: "/", search: closeSearch }); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [dirPath, editing]); const copy = async () => { if (!file.content || file.kind !== "text") return; try { await navigator.clipboard.writeText(editing ? draft : file.content); setCopied(true); setTimeout(() => setCopied(false), 1600); } catch {} }; const save = async () => { setSaving(true); setSaveMsg(null); try { const res = await saveFile({ data: { path: file.relPath, content: draft } }); if (res.ok) { setSaveMsg({ ok: true, text: "Saved" }); setEditing(false); router.invalidate(); setTimeout(() => setSaveMsg(null), 1800); } else setSaveMsg({ ok: false, text: res.error ?? "Could not save" }); } catch { setSaveMsg({ ok: false, text: "Could not save" }); } finally { setSaving(false); } }; const dirty = editing && draft !== (file.content ?? ""); const canEdit = file.kind === "text"; const requestClose = () => { if (dirty && !window.confirm("Discard unsaved changes?")) return; router.navigate({ to: "/", search: closeSearch }); }; const lineCount = (0, import_react.useMemo)(() => file.content && file.kind === "text" ? file.content.split("\n").length : 0, [file.content, file.kind]); return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "fixed inset-0 z-40 flex flex-col bg-black/55 backdrop-blur-sm", style: { overscrollBehavior: "contain" }, children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": "Close file viewer", className: "absolute inset-0 cursor-default", tabIndex: -1, onClick: requestClose }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative mx-auto mt-auto flex h-[92dvh] w-full max-w-3xl flex-col overflow-hidden rounded-t-3xl border-x border-t border-[var(--edge-strong)] bg-[var(--bg-bottom)] shadow-[0_-20px_60px_-15px_rgba(0,0,0,0.7)]", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-center pt-2.5", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-1 w-10 rounded-full bg-white/15" }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2.5 border-b border-[var(--line)] px-4 py-3", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileGlyph, { type: "file", ext: file.ext, className: "size-[18px] shrink-0 text-[var(--gold)]" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate text-[15px] font-medium text-[var(--ink)]", children: file.name }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "nums truncate text-[11px] text-[var(--muted)]", children: [ formatBytes(file.size), file.kind === "text" && lineCount > 0 ? ` · ${lineCount} lines` : "", file.truncated ? " · preview" : "", editing ? " · editing" : "" ] })] }), saveMsg && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { role: "status", "aria-live": "polite", className: cn("shrink-0 rounded-lg px-2 py-1 text-[11px] font-medium", saveMsg.ok ? "bg-[var(--gold-soft)] text-[var(--gold)]" : "bg-red-500/15 text-red-300"), children: saveMsg.text }), editing ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => { if (dirty && !window.confirm("Discard unsaved changes?")) return; setDraft(file.content ?? ""); setEditing(false); setSaveMsg(null); }, className: "shrink-0 rounded-xl border border-[var(--edge)] bg-[var(--panel)] px-3 py-2 text-[13px] font-medium text-[var(--muted)] shadow-xs transition-colors hover:bg-[var(--panel-hover)] hover:text-[var(--ink)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: "Cancel" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", onClick: save, disabled: saving || !dirty, className: "flex shrink-0 items-center gap-1.5 rounded-xl bg-[var(--indigo)] px-3 py-2 text-[13px] font-medium text-white shadow-[0_1px_0_rgba(255,255,255,0.18)_inset,0_4px_12px_-4px_rgba(99,102,241,0.7)] transition-colors hover:bg-[#7679f3] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.97] disabled:opacity-50", style: { touchAction: "manipulation" }, children: [saving ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "size-3.5 animate-spin rounded-full border-2 border-white/40 border-t-white" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Save, { className: "size-3.5" }), saving ? "Saving…" : "Save"] })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ canEdit && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", onClick: () => { setDraft(file.content ?? ""); setEditing(true); setSaveMsg(null); }, "aria-label": "Edit file", className: "flex shrink-0 items-center gap-1.5 rounded-xl border border-[var(--edge)] bg-[var(--panel)] px-3 py-2 text-[13px] font-medium text-[var(--ink)] shadow-xs transition-colors hover:border-[var(--edge-strong)] hover:bg-[var(--panel-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pencil, { className: "size-3.5" }), "Edit"] }), file.kind === "text" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: copy, "aria-label": copied ? "Copied" : "Copy file contents", className: "grid size-9 shrink-0 place-items-center rounded-xl border border-[var(--edge)] bg-[var(--panel)] text-[var(--muted)] shadow-xs transition-colors hover:border-[var(--edge-strong)] hover:bg-[var(--panel-hover)] hover:text-[var(--ink)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: copied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "size-4 text-[var(--gold)]" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "size-4" }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: requestClose, "aria-label": "Close", className: "grid size-9 shrink-0 place-items-center rounded-xl border border-[var(--edge)] bg-[var(--panel)] text-[var(--ink)] shadow-xs transition-colors hover:border-[var(--edge-strong)] hover:bg-[var(--panel-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-4" }) }) ] }) ] }), editing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { value: draft, onChange: (e) => setDraft(e.target.value), autoCorrect: "off", autoCapitalize: "off", autoComplete: "off", spellCheck: false, wrap: "off", className: "scroll-thin flex-1 resize-none bg-transparent px-4 py-3 font-mono text-[13px] leading-[1.65] text-[var(--ink)] outline-none", style: { touchAction: "manipulation", WebkitTextSizeAdjust: "100%" }, "aria-label": `Edit ${file.name}` }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerBody, { file }) ] })] });}function ViewerBody({ file }) { if (file.kind === "image" && file.content) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scroll-thin flex flex-1 items-center justify-center overflow-auto bg-[repeating-conic-gradient(rgba(255,255,255,0.025)_0%_25%,transparent_0%_50%)] bg-[length:20px_20px] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: file.content, alt: file.name, className: "max-h-full max-w-full rounded-lg object-contain shadow-lg" }) }); if (file.kind === "text" && file.content !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scroll-thin flex-1 overflow-auto", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "min-w-full py-3 text-[12.5px] leading-[1.65]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "block", children: file.content.split("\n").map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", className: "nums sticky left-0 mr-3 inline-block w-12 shrink-0 select-none border-r border-[var(--line)] bg-[var(--bg-bottom)] pr-2.5 text-right text-[var(--muted)]/50", children: i + 1 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "whitespace-pre pr-4", children: highlight(line.length ? line : " ", file.ext) })] }, i)) }) }), file.truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "border-t border-[var(--line)] px-4 py-3 text-center text-[12px] text-[var(--muted)]", children: "Preview truncated. File is larger than the inline limit." })] }); const map = { binary: { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HardDrive, { className: "size-5" }), title: "Binary file", body: "This file isn't text and can't be shown here." }, "too-large": { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HardDrive, { className: "size-5" }), title: "File too large", body: "This file exceeds the inline viewer limit." }, denied: { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Lock, { className: "size-5" }), title: "Permission denied", body: "This file can't be read in the current environment." }, missing: { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-5" }), title: "Can't open file", body: "This file is no longer available." } }; const m = map[file.kind] ?? map.missing; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-1 items-center justify-center p-8", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center gap-3 text-center", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "grid size-11 place-items-center rounded-xl bg-[var(--panel)] text-[var(--muted)]", children: m.icon }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[15px] font-medium text-[var(--ink)]", children: m.title }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "max-w-xs text-[13px] text-[var(--muted)]", children: m.body })] })] }) });}function Terminal({ cwd: initialCwd, onClose }) { const [input, setInput] = (0, import_react.useState)(""); const [blocks, setBlocks] = (0, import_react.useState)([]); const [busy, setBusy] = (0, import_react.useState)(false); const [history, setHistory] = (0, import_react.useState)([]); const [histIdx, setHistIdx] = (0, import_react.useState)(-1); const [cwd, setCwd] = (0, import_react.useState)(initialCwd); const scrollRef = (0, import_react.useRef)(null); const inputRef = (0, import_react.useRef)(null); const idRef = (0, import_react.useRef)(0); (0, import_react.useEffect)(() => { const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; }, []); (0, import_react.useEffect)(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [blocks]); const prompt = cwd ? "/" + cwd : "/"; const submit = async () => { const command = input.trim(); if (!command || busy) return; if (command === "clear" || command === "cls") { setBlocks([]); setInput(""); return; } const runCwd = cwd; const id = ++idRef.current; setBlocks((b) => [...b, { id, cwd: runCwd, command, stdout: "", stderr: "", code: null, durationMs: 0, truncated: false, pending: true }]); setHistory((h) => h[h.length - 1] === command ? h : [...h, command]); setHistIdx(-1); setInput(""); setBusy(true); try { const res = await runCommand({ data: { command, cwd: runCwd } }); if (res.newCwd !== null) setCwd(res.newCwd); setBlocks((b) => b.map((bl) => bl.id === id ? { ...bl, ...res, pending: false } : bl)); } catch { setBlocks((b) => b.map((bl) => bl.id === id ? { ...bl, stderr: "Command failed to run.", code: 1, pending: false } : bl)); } finally { setBusy(false); requestAnimationFrame(() => inputRef.current?.focus()); } }; const onKeyDown = (e) => { if (e.key === "Enter") { e.preventDefault(); submit(); } else if (e.key === "ArrowUp") { if (!history.length) return; e.preventDefault(); const next = histIdx < 0 ? history.length - 1 : Math.max(0, histIdx - 1); setHistIdx(next); setInput(history[next]); } else if (e.key === "ArrowDown") { if (histIdx < 0) return; e.preventDefault(); const next = histIdx + 1; if (next >= history.length) { setHistIdx(-1); setInput(""); } else { setHistIdx(next); setInput(history[next]); } } }; return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "fixed inset-0 z-50 flex flex-col bg-[var(--bg-top)]", style: { overscrollBehavior: "contain" }, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2.5 border-b border-[var(--edge)] px-4 py-3 pt-[max(0.75rem,env(safe-area-inset-top))]", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SquareTerminal, { className: "size-[18px] shrink-0 text-[var(--indigo)]" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[14px] font-medium text-[var(--ink)]", children: "Terminal" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate font-mono text-[11px] text-[var(--muted)]", dir: "rtl", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("bdi", { children: prompt }) })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => setBlocks([]), className: "shrink-0 rounded-xl border border-[var(--edge)] bg-[var(--panel)] px-3 py-2 text-[12px] font-medium text-[var(--muted)] shadow-xs transition-colors hover:bg-[var(--panel-hover)] hover:text-[var(--ink)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: "Clear" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: onClose, "aria-label": "Close terminal", className: "grid size-9 shrink-0 place-items-center rounded-xl border border-[var(--edge)] bg-[var(--panel)] text-[var(--ink)] shadow-xs transition-colors hover:bg-[var(--panel-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.97]", style: { touchAction: "manipulation" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-4" }) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: scrollRef, className: "scroll-thin flex-1 overflow-auto px-4 py-3 font-mono text-[12.5px] leading-[1.6]", children: [blocks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-[var(--muted)]", children: [ "Runs bash in ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[var(--gold)]", children: prompt }), ". Try ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[var(--ink)]", children: "ls -la" }), ",", " ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[var(--ink)]", children: "cat file" }), ", ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[var(--ink)]", children: "grep" }), ", or", " ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[var(--ink)]", children: "cd folder" }), " to move around. Type ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[var(--ink)]", children: "clear" }), " to reset." ] }), blocks.map((b) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mb-3", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start gap-2 break-all", children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "shrink-0 select-none whitespace-nowrap text-[var(--muted)]", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[var(--gold)]/70", children: b.cwd ? "/" + b.cwd : "/" }), " ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[var(--indigo)]", children: "$" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "whitespace-pre-wrap break-all text-[var(--ink)]", children: b.command })] }), b.pending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mt-1 text-[var(--muted)]", children: "running…" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ b.stdout && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "mt-1 whitespace-pre-wrap break-all text-[var(--ink)]/90", children: b.stdout }), b.stderr && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "mt-1 whitespace-pre-wrap break-all text-red-300/90", children: b.stderr }), !b.stdout && !b.stderr && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mt-1 text-[var(--muted)]/60", children: "(no output)" }), (b.code ?? 0) !== 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "mt-0.5 text-[11px] text-[var(--muted)]", children: ["exit ", b.code] }), b.truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mt-0.5 text-[11px] text-[var(--muted)]", children: "[output truncated]" }) ] })] }, b.id))] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 border-t border-[var(--edge)] px-3 py-2.5 pb-[max(0.625rem,env(safe-area-inset-bottom))]", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "shrink-0 pl-1 font-mono text-[14px] text-[var(--indigo)]", children: "$" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ref: inputRef, type: "text", value: input, onChange: (e) => setInput(e.target.value), onKeyDown, placeholder: "type a command…", "aria-label": "Terminal command input", autoCorrect: "off", autoCapitalize: "off", autoComplete: "off", spellCheck: false, enterKeyHint: "go", inputMode: "text", autoFocus: true, className: "min-w-0 flex-1 bg-transparent font-mono text-[15px] text-[var(--ink)] outline-none placeholder:text-[var(--muted)]/70", style: { touchAction: "manipulation", WebkitTextSizeAdjust: "100%" } }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: submit, disabled: busy || !input.trim(), "aria-label": "Run command", className: "grid size-9 shrink-0 place-items-center rounded-xl bg-[var(--indigo)] text-white shadow-[0_1px_0_rgba(255,255,255,0.18)_inset] transition-colors hover:bg-[#7679f3] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--indigo)] active:scale-[0.95] disabled:opacity-40", style: { touchAction: "manipulation" }, children: busy ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "size-4 animate-spin rounded-full border-2 border-white/40 border-t-white" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "size-5" }) }) ] }) ] });}//#endregionexport { Explorer as component };