Wherry/frontend/js/components/tabs.js
loki5512344 e188fbef36
feat: FTPS, AppError, Site Manager, tests, multi-window, auto-clear, CI/CD, virtual scroll
- FTPS: реализован FtpsClient через suppaftp + async-native-tls
- AppError: кастомный enum с Serialize вместо Result<T, String>
- Site Manager UI: создание/редактирование/удаление сайтов
- Тесты: 53 тестов (domain, storage, transfer, fs, remote)
- Multi-window: новое окно + сохранение геометрии
- Auto-clear: воркер чистит завершённые задачи по таймеру
- CI/CD: 3-ступенчатый пайплайн (lint→build→test) с кэшированием
- Virtual scroll: fileTable с рендером только видимых строк
- UI: исправлены close-кнопки, color-scheme для select, скриншоты в README
2026-07-11 00:04:13 +02:00

80 lines
2.4 KiB
JavaScript
Raw 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 { iconMarkup } from "../icons.js";
import { closeTab } from "../fsActions.js";
import { openInPane } from "./paneLayout.js";
import { escapeHtml } from "../dom.js";
import { setDraggedTabId } from "../dnd.js";
const STATUS_COLOR = { connected:"text-green", connecting:"text-yellow", error:"text-red", disconnected:"text-hint" };
const STATUS_DOT = { connected:"●", connecting:"◐", error:"×", disconnected:"○" };
export function renderTabBar(container) {
container.innerHTML = "";
// Local tab — always present, not closable
const localTab = createTab("local", "Local", null, false);
container.appendChild(localTab);
for (const tab of state.tabs) {
const el = createTab(tab.id, tab.label, tab.status, true);
container.appendChild(el);
}
}
function createTab(id, label, status, closable) {
const el = document.createElement("button");
el.type = "button";
el.className = "tab";
el.draggable = true;
const inLayout = state.layout.panes.some((p) => p.tabId === (id === "local" ? null : id));
if (inLayout) el.classList.add("active");
if (id !== "local" && status) {
el.innerHTML = `<span class="${STATUS_COLOR[status] ?? "text-hint"}">${STATUS_DOT[status] ?? "○"}</span>`;
}
el.innerHTML += `<span>${escapeHtml(label)}</span>`;
// Drag — send tab-id
el.addEventListener("dragstart", (e) => {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("tab-id", id);
el.classList.add("tab-dragging");
setDraggedTabId(id === "local" ? null : id);
});
el.addEventListener("dragend", () => {
el.classList.remove("tab-dragging");
// Cleanup drop highlights
document.querySelectorAll(".pane-cell.drop-active").forEach((c) => c.classList.remove("drop-active"));
setDraggedTabId(undefined);
});
// Click
el.addEventListener("click", () => {
openInPane(id === "local" ? null : id);
if (id !== "local") {
state.lastRemoteTabId = id;
state.activePane = "remote";
} else {
state.activePane = "local";
}
});
// Close
if (closable) {
const close = document.createElement("button");
close.type = "button";
close.className = "tab-close";
close.innerHTML = iconMarkup("close", 13);
close.title = "Close";
close.addEventListener("click", (e) => {
e.stopPropagation();
closeTab(id);
});
el.appendChild(close);
}
return el;
}
export function renderActivePane() {}