Wherry/frontend/js/components/remotePane.js
2026-07-11 00:01:26 +03:00

145 lines
5.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { state, notify } from "../store.js";
import { t } from "../i18n.js";
import { iconMarkup } from "../icons.js";
import { formatSize } from "../format.js";
import { renderFileTable } from "./fileTable.js";
import { renderBreadcrumbs } from "./breadcrumbs.js";
import { makeDropTarget } from "../dnd.js";
import { openRename, openDelete } from "./opDialogs.js";
import {
navigateRemote,
openRemoteEntry,
handleDropOnRemote,
refreshRemote,
queueDownload,
queueDownloadMany,
} from "../fsActions.js";
const STATUS_ICON = { connected: "●", connecting: "◐", error: "×", disconnected: "○" };
const STATUS_CLASS = { connected: "connected", connecting: "connecting", error: "error", disconnected: "disconnected" };
export function renderRemotePane(container, tab) {
container.innerHTML = `
<div class="path-bar">
<span class="path-bar-label">${t("panels.remoteLabel", { host: tab.params.host })}</span>
<span class="status-dot ${STATUS_CLASS[tab.status] ?? "disconnected"}">${STATUS_ICON[tab.status] ?? "○"}</span>
<div class="breadcrumbs" data-role="breadcrumbs"></div>
<div class="path-bar-right" data-role="loading"></div>
</div>
<div data-role="table" class="file-table-wrap"></div>
<div class="pane-footer">
<span data-role="count" class="text-dim" style="font-size:11px"></span>
<span data-role="size" class="text-hint" style="font-size:11px"></span>
<div class="pane-footer-right">
<button type="button" class="btn icon-btn btn-ghost" data-action="refresh" title="${t("common.refresh")}">${iconMarkup("refresh", 13)}</button>
</div>
</div>
`;
container.querySelector('[data-action="refresh"]').addEventListener("click", () => {
if (!tab.loading) refreshRemote(tab);
});
makeDropTarget(container, {
onDrop: (payload) => handleDropOnRemote(tab, payload, tab.remotePath),
onHoverChange: (hover) => container.classList.toggle("drag-over", hover),
});
updateRemotePane(container, tab);
}
export function updateRemotePane(container, tab) {
renderBreadcrumbs(container.querySelector('[data-role="breadcrumbs"]'), tab.remotePath || "/", (path) =>
navigateRemote(tab, path),
);
container.querySelector('[data-role="loading"]').innerHTML = tab.loading
? `<span>${t("panels.loading")}</span><span class="spinner"></span>`
: "";
renderFileTable(container.querySelector('[data-role="table"]'), {
entries: tab.remoteEntries,
selected: tab.remoteSelected,
anchor: tab.remoteSelectAnchor,
sort: tab.sort,
onSort: (next) => {
tab.sort = next;
notify();
},
onSelect: (names, anchor) => {
tab.remoteSelected = names;
tab.remoteSelectAnchor = anchor;
state.activePane = "remote";
notify();
},
onOpen: (name) => openRemoteEntry(tab, name),
onHoverOpen: (entry) => openRemoteEntry(tab, entry.name),
dragPayload: (entry) => {
if (entry.name === ".." || tab.status !== "connected") return null;
const group =
tab.remoteSelected.includes(entry.name) && tab.remoteSelected.length > 1
? tab.remoteEntries.filter((e) => tab.remoteSelected.includes(e.name))
: [entry];
return {
source: "remote",
connectionId: tab.id,
items: group.map((e) => ({ path: e.path, name: e.name, kind: e.kind })),
};
},
onDropOnEntry: (entry, payload) => handleDropOnRemote(tab, payload, entry.path),
contextMenuItems: (entry, selectedEntries) => {
if (selectedEntries.length > 1) {
const files = selectedEntries.filter((e) => e.kind === "file");
return [
{
label: t("common.delete") + ` (${selectedEntries.length})`,
icon: "trash",
danger: true,
onClick: () => openDeleteRemote(selectedEntries),
},
...(files.length > 0
? [
"sep",
{
label: t("panels.downloadToLocal") + ` (${files.length})`,
icon: "download",
onClick: () => queueDownloadMany(tab, files),
},
]
: []),
];
}
return [
{ label: t("common.open"), icon: "folder", onClick: () => openRemoteEntry(tab, entry.name) },
{ label: t("common.rename"), icon: "pen", onClick: () => openRenameRemote(entry) },
{ label: t("common.delete"), icon: "trash", danger: true, onClick: () => openDeleteRemote([entry]) },
"sep",
{ label: t("common.copyPath"), icon: "copy", onClick: () => copyPath(entry.path) },
...(entry.kind === "file"
? [{ label: t("panels.downloadToLocal"), icon: "download", onClick: () => queueDownload(tab, entry) }]
: []),
];
},
});
const count = tab.remoteEntries.filter((e) => e.name !== "..").length;
const totalSize = tab.remoteEntries.reduce((sum, e) => sum + (e.size ?? 0), 0);
container.querySelector('[data-role="count"]').textContent = tab.loading
? t("panels.loading")
: t("panels.countItems", { n: count });
container.querySelector('[data-role="size"]').textContent = totalSize > 0 ? `(${formatSize(totalSize)})` : "";
}
function openRenameRemote(entry) {
state.opTarget = "remote";
openRename(entry.name);
}
function openDeleteRemote(entries) {
state.opTarget = "remote";
openDelete(entries.map((e) => e.name));
}
function copyPath(path) {
navigator.clipboard?.writeText(path);
state.statusMessage = t("panels.pathCopied");
notify();
}