Add Phase 2 remote browse scaffolding for /Clients
This commit is contained in:
+86
-19
@@ -141,6 +141,10 @@ const VALID_THEME_FAMILIES = [
|
||||
"fluent-neon",
|
||||
];
|
||||
const VALID_COLOR_MODES = ["dark", "light"];
|
||||
const VIRTUAL_SOURCES = [
|
||||
{ path: "/Volumes", label: "Volumes" },
|
||||
{ path: "/Clients", label: "Clients" },
|
||||
];
|
||||
let searchState = {
|
||||
pane: "left",
|
||||
path: "/Volumes",
|
||||
@@ -200,6 +204,56 @@ function activePaneState() {
|
||||
return paneState(state.activePane);
|
||||
}
|
||||
|
||||
function sourceRootForPath(path) {
|
||||
const normalized = (path || "").trim();
|
||||
if (normalized === "/Clients" || normalized.startsWith("/Clients/")) {
|
||||
return "/Clients";
|
||||
}
|
||||
return "/Volumes";
|
||||
}
|
||||
|
||||
function isRemoteBrowsePath(path) {
|
||||
return sourceRootForPath(path) === "/Clients";
|
||||
}
|
||||
|
||||
function syncSourceSwitchers() {
|
||||
["left", "right"].forEach((pane) => {
|
||||
const container = document.getElementById(`${pane}-source-switcher`);
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
const activeSource = sourceRootForPath(paneState(pane).currentPath);
|
||||
[...container.querySelectorAll("button[data-source-path]")].forEach((button) => {
|
||||
const isActive = button.dataset.sourcePath === activeSource;
|
||||
button.disabled = isActive;
|
||||
button.setAttribute("aria-pressed", isActive ? "true" : "false");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function ensureSourceSwitchers() {
|
||||
["left", "right"].forEach((pane) => {
|
||||
const toolbar = document.querySelector(`#${pane}-pane .pane-topbar`);
|
||||
if (!toolbar || document.getElementById(`${pane}-source-switcher`)) {
|
||||
return;
|
||||
}
|
||||
const container = document.createElement("div");
|
||||
container.id = `${pane}-source-switcher`;
|
||||
container.className = "pane-source-switcher";
|
||||
VIRTUAL_SOURCES.forEach((source) => {
|
||||
const button = createButton(source.label, () => {
|
||||
setActivePane(pane);
|
||||
navigateTo(pane, source.path);
|
||||
});
|
||||
button.type = "button";
|
||||
button.dataset.sourcePath = source.path;
|
||||
container.append(button);
|
||||
});
|
||||
toolbar.prepend(container);
|
||||
});
|
||||
syncSourceSwitchers();
|
||||
}
|
||||
|
||||
function setStatus(msg) {
|
||||
document.getElementById("status").textContent = msg;
|
||||
}
|
||||
@@ -716,6 +770,7 @@ function openContextMenu(pane, entry, event) {
|
||||
const items = selectedPathsSet.has(entry.path)
|
||||
? selectedItems.map((item) => ({ ...item }))
|
||||
: [selectedEntryFromItem(entry)];
|
||||
const remoteSelection = items.some((item) => isRemoteBrowsePath(item.path));
|
||||
|
||||
contextMenuState.open = true;
|
||||
contextMenuState.pane = pane;
|
||||
@@ -723,26 +778,28 @@ function openContextMenu(pane, entry, event) {
|
||||
contextMenuState.anchorPath = entry.path;
|
||||
|
||||
const isMulti = items.length > 1;
|
||||
const openableSingle = items.length === 1 && isOpenableSelection(items[0]);
|
||||
const editableSingle = items.length === 1 && isEditableSelection(items[0]);
|
||||
const downloadableSelection = items.length > 0;
|
||||
const openableSingle = items.length === 1 && (!remoteSelection || items[0].kind === "directory") && isOpenableSelection(items[0]);
|
||||
const editableSingle = items.length === 1 && !remoteSelection && isEditableSelection(items[0]);
|
||||
const downloadableSelection = items.length > 0 && !remoteSelection;
|
||||
elements.scope.textContent = isMulti ? "Multi-selection" : "Single item";
|
||||
elements.target.textContent = isMulti ? `${items.length} selected items` : entry.name;
|
||||
elements.openButton.classList.toggle("hidden", isMulti);
|
||||
elements.openButton.disabled = !openableSingle;
|
||||
elements.editButton.classList.toggle("hidden", isMulti || items.length !== 1 || items[0].kind !== "file");
|
||||
elements.editButton.classList.toggle("hidden", isMulti || items.length !== 1 || items[0].kind !== "file" || remoteSelection);
|
||||
elements.editButton.disabled = !editableSingle;
|
||||
elements.downloadButton.classList.remove("hidden");
|
||||
elements.downloadButton.classList.toggle("hidden", remoteSelection);
|
||||
elements.downloadButton.disabled = !downloadableSelection;
|
||||
elements.renameButton.classList.toggle("hidden", isMulti);
|
||||
elements.renameButton.classList.toggle("hidden", isMulti || remoteSelection);
|
||||
elements.duplicateButton.classList.remove("hidden");
|
||||
elements.duplicateButton.disabled = items.length === 0;
|
||||
elements.duplicateButton.disabled = remoteSelection || items.length === 0;
|
||||
elements.copyButton.classList.remove("hidden");
|
||||
elements.copyButton.disabled = items.length === 0;
|
||||
elements.copyButton.disabled = remoteSelection || items.length === 0;
|
||||
elements.moveButton.classList.remove("hidden");
|
||||
elements.moveButton.disabled = remoteSelection || items.length === 0;
|
||||
elements.deleteButton.classList.remove("hidden");
|
||||
elements.propertiesButton.classList.remove("hidden");
|
||||
elements.propertiesButton.disabled = items.length === 0;
|
||||
elements.deleteButton.disabled = remoteSelection || items.length === 0;
|
||||
elements.propertiesButton.classList.toggle("hidden", remoteSelection);
|
||||
elements.propertiesButton.disabled = remoteSelection || items.length === 0;
|
||||
|
||||
const menuWidth = 220;
|
||||
const menuHeight = 120;
|
||||
@@ -2050,12 +2107,17 @@ function updateActionButtons() {
|
||||
const hasSelection = count > 0;
|
||||
const exactlyOne = count === 1;
|
||||
const allFiles = hasSelection && selectedItems.every((item) => item.kind === "file");
|
||||
document.getElementById("view-btn").disabled = !exactlyOne || !allFiles;
|
||||
document.getElementById("edit-btn").disabled = !exactlyOne || !allFiles || !isEditableSelection(selectedItems[0] || null);
|
||||
document.getElementById("rename-btn").disabled = !exactlyOne;
|
||||
document.getElementById("delete-btn").disabled = !hasSelection;
|
||||
document.getElementById("copy-btn").disabled = !hasSelection;
|
||||
document.getElementById("move-btn").disabled = !hasSelection;
|
||||
const remoteBrowse = isRemoteBrowsePath(activePaneState().currentPath);
|
||||
document.getElementById("view-btn").disabled = remoteBrowse || !exactlyOne || !allFiles;
|
||||
document.getElementById("edit-btn").disabled = remoteBrowse || !exactlyOne || !allFiles || !isEditableSelection(selectedItems[0] || null);
|
||||
document.getElementById("rename-btn").disabled = remoteBrowse || !exactlyOne;
|
||||
document.getElementById("delete-btn").disabled = remoteBrowse || !hasSelection;
|
||||
document.getElementById("copy-btn").disabled = remoteBrowse || !hasSelection;
|
||||
document.getElementById("move-btn").disabled = remoteBrowse || !hasSelection;
|
||||
document.getElementById("mkdir-btn").disabled = remoteBrowse;
|
||||
document.getElementById("upload-btn").disabled = remoteBrowse;
|
||||
document.getElementById("upload-menu-toggle").disabled = remoteBrowse;
|
||||
document.getElementById("upload-folder-btn").disabled = remoteBrowse;
|
||||
}
|
||||
|
||||
function isEditableSelection(item) {
|
||||
@@ -2208,7 +2270,7 @@ function currentParentPath(path) {
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (normalized === "/Volumes") {
|
||||
if (normalized === "/Volumes" || normalized === "/Clients") {
|
||||
return null;
|
||||
}
|
||||
if (normalized.startsWith("/")) {
|
||||
@@ -2287,16 +2349,17 @@ function renderBreadcrumbs(pane, path) {
|
||||
const isHostPath = normalized.startsWith("/");
|
||||
const parts = normalized.split("/").filter(Boolean);
|
||||
if (isHostPath) {
|
||||
const rootTarget = parts.length > 0 ? `/${parts[0]}` : "/Volumes";
|
||||
const rootCrumb = createButton("/", () => {
|
||||
setActivePane(pane);
|
||||
navigateTo(pane, "/Volumes");
|
||||
navigateTo(pane, rootTarget);
|
||||
});
|
||||
rootCrumb.type = "button";
|
||||
rootCrumb.onclick = (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setActivePane(pane);
|
||||
navigateTo(pane, "/Volumes");
|
||||
navigateTo(pane, rootTarget);
|
||||
};
|
||||
nav.append(rootCrumb);
|
||||
if (parts.length > 0) {
|
||||
@@ -2619,6 +2682,7 @@ async function loadBrowsePane(pane) {
|
||||
});
|
||||
const data = await apiRequest("GET", `/api/browse?${query.toString()}`);
|
||||
model.currentPath = data.path;
|
||||
syncSourceSwitchers();
|
||||
renderBreadcrumbs(pane, data.path);
|
||||
|
||||
const visibleItems = [];
|
||||
@@ -2682,6 +2746,8 @@ function navigateTo(pane, path) {
|
||||
model.currentRowIndex = 0;
|
||||
clearSelectionAnchor(pane);
|
||||
setSelectedItem(pane, null);
|
||||
syncSourceSwitchers();
|
||||
updateActionButtons();
|
||||
loadBrowsePane(pane);
|
||||
}
|
||||
|
||||
@@ -5305,6 +5371,7 @@ async function init() {
|
||||
setError("actions-error", "");
|
||||
applyTheme("default", "dark");
|
||||
setActivePane("left");
|
||||
ensureSourceSwitchers();
|
||||
setupEvents();
|
||||
await loadSettings();
|
||||
applyTheme(settingsState.selectedTheme, settingsState.selectedColorMode);
|
||||
|
||||
Reference in New Issue
Block a user