upload volledige repo
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
let state = {
|
||||
panes: {
|
||||
left: {
|
||||
currentPath: "storage1",
|
||||
showHidden: false,
|
||||
selectedItem: null,
|
||||
selectedItems: [],
|
||||
},
|
||||
right: {
|
||||
currentPath: "storage1",
|
||||
showHidden: false,
|
||||
selectedItem: null,
|
||||
selectedItems: [],
|
||||
},
|
||||
},
|
||||
activePane: "left",
|
||||
selectedTaskId: null,
|
||||
};
|
||||
|
||||
function paneState(pane) {
|
||||
return state.panes[pane];
|
||||
}
|
||||
|
||||
function otherPane(pane) {
|
||||
return pane === "left" ? "right" : "left";
|
||||
}
|
||||
|
||||
function activePaneState() {
|
||||
return paneState(state.activePane);
|
||||
}
|
||||
|
||||
function setStatus(msg) {
|
||||
document.getElementById("status").textContent = msg;
|
||||
}
|
||||
|
||||
function setError(id, msg) {
|
||||
document.getElementById(id).textContent = msg || "";
|
||||
}
|
||||
|
||||
function setActionError(action, err) {
|
||||
setError("actions-error", `${action}: ${err.message}`);
|
||||
}
|
||||
|
||||
async function apiRequest(method, url, body) {
|
||||
const options = { method, headers: {} };
|
||||
if (body !== undefined) {
|
||||
options.headers["Content-Type"] = "application/json";
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
const response = await fetch(url, options);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const error = data.error || {};
|
||||
throw new Error(error.message || `HTTP ${response.status}`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function createButton(text, onClick) {
|
||||
const button = document.createElement("button");
|
||||
button.textContent = text;
|
||||
button.onclick = onClick;
|
||||
return button;
|
||||
}
|
||||
|
||||
function setActivePane(pane) {
|
||||
state.activePane = pane;
|
||||
document.getElementById("active-pane-label").textContent = pane;
|
||||
document.getElementById("left-pane").classList.toggle("active-pane", pane === "left");
|
||||
document.getElementById("right-pane").classList.toggle("active-pane", pane === "right");
|
||||
updateActionButtons();
|
||||
}
|
||||
|
||||
function setSelectedItem(pane, item) {
|
||||
const model = paneState(pane);
|
||||
model.selectedItem = item;
|
||||
model.selectedItems = item ? [item] : [];
|
||||
updateActionButtons();
|
||||
}
|
||||
|
||||
function selectedPaths(pane) {
|
||||
return paneState(pane).selectedItems.map((item) => item.path);
|
||||
}
|
||||
|
||||
function setSingleSelection(pane, item) {
|
||||
setSelectedItem(pane, item);
|
||||
}
|
||||
|
||||
function toggleSelection(pane, item) {
|
||||
const model = paneState(pane);
|
||||
const index = model.selectedItems.findIndex((selected) => selected.path === item.path);
|
||||
if (index >= 0) {
|
||||
const removed = model.selectedItems[index];
|
||||
model.selectedItems.splice(index, 1);
|
||||
if (model.selectedItem && model.selectedItem.path === removed.path) {
|
||||
model.selectedItem = model.selectedItems.length > 0 ? model.selectedItems[model.selectedItems.length - 1] : null;
|
||||
}
|
||||
} else {
|
||||
model.selectedItems.push(item);
|
||||
model.selectedItem = item;
|
||||
}
|
||||
updateActionButtons();
|
||||
}
|
||||
|
||||
function updateActionButtons() {
|
||||
const selected = activePaneState().selectedItem;
|
||||
const hasSelection = Boolean(selected);
|
||||
const isFile = hasSelection && selected.kind === "file";
|
||||
document.getElementById("rename-btn").disabled = !hasSelection;
|
||||
document.getElementById("delete-btn").disabled = !hasSelection;
|
||||
document.getElementById("copy-btn").disabled = !isFile;
|
||||
document.getElementById("move-btn").disabled = !isFile;
|
||||
}
|
||||
|
||||
function currentParentPath(path) {
|
||||
if (!path.includes("/")) {
|
||||
return null;
|
||||
}
|
||||
const segments = path.split("/");
|
||||
if (segments.length === 2) {
|
||||
return segments[0];
|
||||
}
|
||||
return segments.slice(0, -1).join("/");
|
||||
}
|
||||
|
||||
function renderBreadcrumbs(pane, path) {
|
||||
const nav = document.getElementById(`${pane}-breadcrumbs`);
|
||||
nav.innerHTML = "";
|
||||
const parts = path.split("/");
|
||||
let aggregate = "";
|
||||
for (let i = 0; i < parts.length; i += 1) {
|
||||
aggregate = i === 0 ? parts[i] : `${aggregate}/${parts[i]}`;
|
||||
const crumb = createButton(parts[i], () => {
|
||||
setActivePane(pane);
|
||||
navigateTo(pane, aggregate);
|
||||
});
|
||||
crumb.type = "button";
|
||||
crumb.onclick = (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setActivePane(pane);
|
||||
navigateTo(pane, aggregate);
|
||||
};
|
||||
nav.append(crumb);
|
||||
if (i < parts.length - 1) {
|
||||
const sep = document.createElement("span");
|
||||
sep.textContent = "/";
|
||||
nav.append(sep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatModified(isoString) {
|
||||
if (!isoString) {
|
||||
return "-";
|
||||
}
|
||||
const d = new Date(isoString);
|
||||
if (Number.isNaN(d.getTime())) {
|
||||
return isoString;
|
||||
}
|
||||
const date = d.toLocaleDateString(undefined, { year: "2-digit", month: "2-digit", day: "2-digit" });
|
||||
const time = d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
|
||||
return `${date} ${time}`;
|
||||
}
|
||||
|
||||
function createBrowseItem(pane, entry, kind) {
|
||||
const li = document.createElement("li");
|
||||
li.className = "selectable";
|
||||
const paths = selectedPaths(pane);
|
||||
if (paths.includes(entry.path)) {
|
||||
li.classList.add("is-selected");
|
||||
}
|
||||
|
||||
li.onclick = () => {
|
||||
setActivePane(pane);
|
||||
setSingleSelection(pane, { path: entry.path, name: entry.name, kind });
|
||||
loadBrowsePane(pane);
|
||||
};
|
||||
|
||||
const marker = document.createElement("input");
|
||||
marker.type = "checkbox";
|
||||
marker.className = "select-marker";
|
||||
marker.checked = paths.includes(entry.path);
|
||||
marker.onclick = (ev) => {
|
||||
ev.stopPropagation();
|
||||
setActivePane(pane);
|
||||
toggleSelection(pane, { path: entry.path, name: entry.name, kind });
|
||||
loadBrowsePane(pane);
|
||||
};
|
||||
li.append(marker);
|
||||
|
||||
const name = document.createElement("span");
|
||||
name.className = `entry-name ${kind === "directory" ? "entry-dir" : "entry-file"}`;
|
||||
|
||||
if (kind === "directory") {
|
||||
const open = document.createElement("button");
|
||||
open.className = "dir-link";
|
||||
open.textContent = `${entry.name}/`;
|
||||
open.type = "button";
|
||||
open.onclick = (ev) => {
|
||||
ev.stopPropagation();
|
||||
setActivePane(pane);
|
||||
navigateTo(pane, entry.path);
|
||||
};
|
||||
name.append(open);
|
||||
} else {
|
||||
const fileName = document.createElement("span");
|
||||
fileName.textContent = entry.name;
|
||||
fileName.onclick = (ev) => {
|
||||
ev.stopPropagation();
|
||||
setActivePane(pane);
|
||||
setSingleSelection(pane, { path: entry.path, name: entry.name, kind });
|
||||
loadBrowsePane(pane);
|
||||
};
|
||||
name.append(fileName);
|
||||
}
|
||||
li.append(name);
|
||||
|
||||
const size = document.createElement("span");
|
||||
size.className = "entry-size";
|
||||
size.textContent = kind === "directory" ? "-" : String(entry.size);
|
||||
li.append(size);
|
||||
|
||||
const modified = document.createElement("span");
|
||||
modified.className = "entry-modified";
|
||||
modified.textContent = formatModified(entry.modified);
|
||||
li.append(modified);
|
||||
return li;
|
||||
}
|
||||
|
||||
async function loadBrowsePane(pane) {
|
||||
setError(`${pane}-browse-error`, "");
|
||||
try {
|
||||
const model = paneState(pane);
|
||||
const query = new URLSearchParams({
|
||||
path: model.currentPath,
|
||||
show_hidden: String(model.showHidden),
|
||||
});
|
||||
const data = await apiRequest("GET", `/api/browse?${query.toString()}`);
|
||||
model.currentPath = data.path;
|
||||
document.getElementById(`${pane}-current-path`).textContent = data.path;
|
||||
renderBreadcrumbs(pane, data.path);
|
||||
|
||||
const items = document.getElementById(`${pane}-items`);
|
||||
items.innerHTML = "";
|
||||
|
||||
const parent = currentParentPath(data.path);
|
||||
if (parent) {
|
||||
const up = document.createElement("li");
|
||||
up.className = "selectable";
|
||||
up.append(document.createElement("span"));
|
||||
const upName = document.createElement("button");
|
||||
upName.type = "button";
|
||||
upName.className = "dir-link";
|
||||
upName.textContent = "../";
|
||||
upName.onclick = () => navigateTo(pane, parent);
|
||||
const upNameCell = document.createElement("span");
|
||||
upNameCell.className = "entry-name entry-dir";
|
||||
upNameCell.append(upName);
|
||||
up.append(upNameCell);
|
||||
const upSize = document.createElement("span");
|
||||
upSize.className = "entry-size";
|
||||
upSize.textContent = "-";
|
||||
up.append(upSize);
|
||||
const upModified = document.createElement("span");
|
||||
upModified.className = "entry-modified";
|
||||
upModified.textContent = "-";
|
||||
up.append(upModified);
|
||||
items.append(up);
|
||||
}
|
||||
|
||||
const visiblePaths = new Set();
|
||||
for (const entry of data.directories) {
|
||||
visiblePaths.add(entry.path);
|
||||
items.append(createBrowseItem(pane, entry, "directory"));
|
||||
}
|
||||
for (const entry of data.files) {
|
||||
visiblePaths.add(entry.path);
|
||||
items.append(createBrowseItem(pane, entry, "file"));
|
||||
}
|
||||
|
||||
model.selectedItems = model.selectedItems.filter((item) => visiblePaths.has(item.path));
|
||||
if (model.selectedItem && !visiblePaths.has(model.selectedItem.path)) {
|
||||
model.selectedItem = model.selectedItems.length > 0 ? model.selectedItems[model.selectedItems.length - 1] : null;
|
||||
}
|
||||
updateActionButtons();
|
||||
setStatus(`Loaded ${pane}: ${data.path}`);
|
||||
} catch (err) {
|
||||
setError(`${pane}-browse-error`, `Browse: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(pane, path) {
|
||||
paneState(pane).currentPath = path;
|
||||
setSelectedItem(pane, null);
|
||||
loadBrowsePane(pane);
|
||||
}
|
||||
|
||||
async function createFolderForPane(pane) {
|
||||
setActivePane(pane);
|
||||
const name = window.prompt("Folder name");
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
setError(`${pane}-browse-error`, "");
|
||||
try {
|
||||
await apiRequest("POST", "/api/files/mkdir", {
|
||||
parent_path: paneState(pane).currentPath,
|
||||
name,
|
||||
});
|
||||
await loadBrowsePane(pane);
|
||||
} catch (err) {
|
||||
setError(`${pane}-browse-error`, `Create folder: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function createFolderForActivePane() {
|
||||
await createFolderForPane(state.activePane);
|
||||
}
|
||||
|
||||
async function renameSelected() {
|
||||
const pane = state.activePane;
|
||||
const selected = paneState(pane).selectedItem;
|
||||
if (!selected) {
|
||||
return;
|
||||
}
|
||||
const newName = window.prompt("New name", selected.name);
|
||||
if (!newName) {
|
||||
return;
|
||||
}
|
||||
setError("actions-error", "");
|
||||
try {
|
||||
await apiRequest("POST", "/api/files/rename", {
|
||||
path: selected.path,
|
||||
new_name: newName,
|
||||
});
|
||||
setSelectedItem(pane, null);
|
||||
await loadBrowsePane(pane);
|
||||
} catch (err) {
|
||||
setActionError("Rename", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSelected() {
|
||||
const pane = state.activePane;
|
||||
const selected = paneState(pane).selectedItem;
|
||||
if (!selected) {
|
||||
return;
|
||||
}
|
||||
if (!window.confirm(`Delete ${selected.path}?`)) {
|
||||
return;
|
||||
}
|
||||
setError("actions-error", "");
|
||||
try {
|
||||
await apiRequest("POST", "/api/files/delete", { path: selected.path });
|
||||
setSelectedItem(pane, null);
|
||||
await loadBrowsePane(pane);
|
||||
} catch (err) {
|
||||
setActionError("Delete", err);
|
||||
}
|
||||
}
|
||||
|
||||
function defaultDestination(sourcePath, targetBasePath) {
|
||||
const sourceName = sourcePath.slice(sourcePath.lastIndexOf("/") + 1);
|
||||
return `${targetBasePath}/${sourceName}`;
|
||||
}
|
||||
|
||||
async function startCopySelected() {
|
||||
const sourcePane = state.activePane;
|
||||
const destinationPane = otherPane(sourcePane);
|
||||
const selected = paneState(sourcePane).selectedItem;
|
||||
if (!selected || selected.kind !== "file") {
|
||||
return;
|
||||
}
|
||||
const destination = window.prompt(
|
||||
"Copy destination (full path)",
|
||||
defaultDestination(selected.path, paneState(destinationPane).currentPath),
|
||||
);
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
setError("actions-error", "");
|
||||
try {
|
||||
const result = await apiRequest("POST", "/api/files/copy", {
|
||||
source: selected.path,
|
||||
destination,
|
||||
});
|
||||
state.selectedTaskId = result.task_id;
|
||||
setStatus(`Copy task queued: ${result.task_id}`);
|
||||
await Promise.all([loadBrowsePane("left"), loadBrowsePane("right")]);
|
||||
} catch (err) {
|
||||
setActionError("Copy", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function startMoveSelected() {
|
||||
const sourcePane = state.activePane;
|
||||
const destinationPane = otherPane(sourcePane);
|
||||
const selected = paneState(sourcePane).selectedItem;
|
||||
if (!selected || selected.kind !== "file") {
|
||||
return;
|
||||
}
|
||||
const destination = window.prompt(
|
||||
"Move destination (full path)",
|
||||
defaultDestination(selected.path, paneState(destinationPane).currentPath),
|
||||
);
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
setError("actions-error", "");
|
||||
try {
|
||||
const result = await apiRequest("POST", "/api/files/move", {
|
||||
source: selected.path,
|
||||
destination,
|
||||
});
|
||||
state.selectedTaskId = result.task_id;
|
||||
setSelectedItem(sourcePane, null);
|
||||
setStatus(`Move task queued: ${result.task_id}`);
|
||||
await Promise.all([loadBrowsePane("left"), loadBrowsePane("right")]);
|
||||
} catch (err) {
|
||||
setActionError("Move", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function addBookmark() {
|
||||
const pane = state.activePane;
|
||||
const path = paneState(pane).currentPath;
|
||||
const label = window.prompt("Bookmark label", path);
|
||||
if (!label) {
|
||||
return;
|
||||
}
|
||||
setError("actions-error", "");
|
||||
try {
|
||||
await apiRequest("POST", "/api/bookmarks", { path, label });
|
||||
setStatus(`Bookmark added for ${path}`);
|
||||
} catch (err) {
|
||||
setActionError("Add bookmark", err);
|
||||
}
|
||||
}
|
||||
|
||||
function setupPaneEvents(pane) {
|
||||
document.getElementById(`${pane}-pane`).onclick = () => setActivePane(pane);
|
||||
document.getElementById(`${pane}-hidden-toggle`).onchange = (ev) => {
|
||||
setActivePane(pane);
|
||||
paneState(pane).showHidden = ev.target.checked;
|
||||
loadBrowsePane(pane);
|
||||
};
|
||||
}
|
||||
|
||||
function setupEvents() {
|
||||
setupPaneEvents("left");
|
||||
setupPaneEvents("right");
|
||||
document.getElementById("rename-btn").onclick = renameSelected;
|
||||
document.getElementById("delete-btn").onclick = deleteSelected;
|
||||
document.getElementById("copy-btn").onclick = startCopySelected;
|
||||
document.getElementById("move-btn").onclick = startMoveSelected;
|
||||
document.getElementById("mkdir-btn").onclick = createFolderForActivePane;
|
||||
document.getElementById("add-bookmark-btn").onclick = addBookmark;
|
||||
}
|
||||
|
||||
async function init() {
|
||||
setError("actions-error", "");
|
||||
setActivePane("left");
|
||||
setupEvents();
|
||||
await Promise.all([loadBrowsePane("left"), loadBrowsePane("right")]);
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -0,0 +1,78 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WebManager v2</title>
|
||||
<link rel="stylesheet" href="/ui/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-shell">
|
||||
<header id="title-zone">
|
||||
<h1>WebManager v2</h1>
|
||||
<div id="status"></div>
|
||||
</header>
|
||||
|
||||
<main id="workspace" class="workspace">
|
||||
<section class="panel pane" id="left-pane" data-pane="left">
|
||||
<div class="pane-header">
|
||||
<div class="toolbar compact-toolbar pane-topbar">
|
||||
<h2 class="pane-title">Left</h2>
|
||||
<label class="checkbox"><input id="left-hidden-toggle" type="checkbox">Hidden</label>
|
||||
</div>
|
||||
<div class="pathline compact-line">C:<code id="left-current-path"></code></div>
|
||||
<nav id="left-breadcrumbs" class="breadcrumbs" aria-label="Left breadcrumb"></nav>
|
||||
<div id="left-browse-error" class="error"></div>
|
||||
</div>
|
||||
|
||||
<div class="pane-content">
|
||||
<div class="list-grid-header">
|
||||
<span class="col-sel"></span>
|
||||
<span class="col-name">Name</span>
|
||||
<span class="col-size">Size</span>
|
||||
<span class="col-modified">Modified</span>
|
||||
</div>
|
||||
<ul id="left-items" class="list"></ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel pane" id="right-pane" data-pane="right">
|
||||
<div class="pane-header">
|
||||
<div class="toolbar compact-toolbar pane-topbar">
|
||||
<h2 class="pane-title">Right</h2>
|
||||
<label class="checkbox"><input id="right-hidden-toggle" type="checkbox">Hidden</label>
|
||||
</div>
|
||||
<div class="pathline compact-line">C:<code id="right-current-path"></code></div>
|
||||
<nav id="right-breadcrumbs" class="breadcrumbs" aria-label="Right breadcrumb"></nav>
|
||||
<div id="right-browse-error" class="error"></div>
|
||||
</div>
|
||||
|
||||
<div class="pane-content">
|
||||
<div class="list-grid-header">
|
||||
<span class="col-sel"></span>
|
||||
<span class="col-name">Name</span>
|
||||
<span class="col-size">Size</span>
|
||||
<span class="col-modified">Modified</span>
|
||||
</div>
|
||||
<ul id="right-items" class="list"></ul>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<section id="footer-bar">
|
||||
<div class="toolbar compact-toolbar">
|
||||
<div class="pathline compact-line">A:<code id="active-pane-label">left</code></div>
|
||||
<button id="rename-btn" disabled>Rename</button>
|
||||
<button id="delete-btn" disabled>Delete</button>
|
||||
<button id="copy-btn" disabled>Copy</button>
|
||||
<button id="move-btn" disabled>Move</button>
|
||||
<button id="mkdir-btn">Mkdir</button>
|
||||
<button id="add-bookmark-btn">Add bookmark</button>
|
||||
</div>
|
||||
<div id="actions-error" class="error"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="/ui/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,300 @@
|
||||
:root {
|
||||
--bg: #f4f7fb;
|
||||
--panel: #ffffff;
|
||||
--border: #d7deea;
|
||||
--text: #192232;
|
||||
--muted: #5c687c;
|
||||
--error: #b11d1d;
|
||||
--accent: #1b5ec9;
|
||||
--active-border: #1b5ec9;
|
||||
--active-bg: #ffffff;
|
||||
--bottom-reserve: 0px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", Tahoma, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app-shell {
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
#title-zone {
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pane.active-pane {
|
||||
border-color: var(--active-border);
|
||||
box-shadow: 0 0 0 1px var(--active-border) inset;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.pane-header {
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pane-content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.compact-toolbar {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.pane-topbar {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.pane-title {
|
||||
min-width: 42px;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
input, button {
|
||||
font: inherit;
|
||||
padding: 4px 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid var(--border);
|
||||
background: #f8fafc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pathline {
|
||||
color: var(--muted);
|
||||
margin-bottom: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.compact-line {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-bottom: 3px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.breadcrumbs button {
|
||||
padding: 1px 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.list-label {
|
||||
font-size: 11px;
|
||||
margin: 0;
|
||||
padding: 2px 0;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.list-grid-header {
|
||||
display: grid;
|
||||
grid-template-columns: 14px minmax(0, 1fr) 88px 138px;
|
||||
gap: 6px;
|
||||
padding: 2px 0 4px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 2px;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.col-size,
|
||||
.col-modified {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.list li {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 5px 0 4px 0;
|
||||
display: grid;
|
||||
grid-template-columns: 14px minmax(0, 1fr) 88px 138px;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list li.selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list li.is-selected {
|
||||
background: #e9f0fd;
|
||||
}
|
||||
|
||||
.select-marker {
|
||||
appearance: none;
|
||||
width: 10px;
|
||||
min-width: 10px;
|
||||
height: 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list li.is-selected .select-marker {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.select-marker:checked {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.entry-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.entry-name.entry-dir {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dir-link {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.entry-size,
|
||||
.entry-modified {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error);
|
||||
min-height: 12px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#status {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#footer-bar {
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
padding: 4px 10px 2px 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.workspace {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
|
||||
.list-grid-header,
|
||||
.list li {
|
||||
grid-template-columns: 14px minmax(0, 1fr) 70px 112px;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user