zcbot/web/static/js/layout.js

121 lines
5.2 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.

// 三栏布局:pane 折叠(rail 模式 + localStorage)+ 拖拽 splitter + 手机单列视图。
// 顶层即绑定 toggle/splitter/mobile-tab 事件并应用初始状态(import 时执行,DOM 已就绪)。
// 只 setMobileView / mqPhone 对外(selectTask 在手机端选中任务时切到对话面板)。
import { $ } from "./dom.js";
import { LS_LEFT_COLLAPSED, LS_RIGHT_COLLAPSED, LS_LEFT_WIDTH, LS_RIGHT_WIDTH } from "./state.js";
// ───── pane 折叠 + splitters(rail 模式 + localStorage 持久化) ─────
const PANE_W = { left: { min: 220, max: 560, def: 320 }, right: { min: 220, max: 560, def: 320 } };
function clampPaneWidth(side, value) {
const cfg = PANE_W[side];
const n = Number(value);
if (!Number.isFinite(n)) return cfg.def;
return Math.max(cfg.min, Math.min(cfg.max, Math.round(n)));
}
function applyPaneWidth(side, value) {
const width = clampPaneWidth(side, value);
$("app").style.setProperty(side === "left" ? "--left-pane-width" : "--right-pane-width", width + "px");
localStorage.setItem(side === "left" ? LS_LEFT_WIDTH : LS_RIGHT_WIDTH, String(width));
}
function restorePaneWidths() {
applyPaneWidth("left", localStorage.getItem(LS_LEFT_WIDTH) || PANE_W.left.def);
applyPaneWidth("right", localStorage.getItem(LS_RIGHT_WIDTH) || PANE_W.right.def);
}
// 折叠 = pane 收成 40px rail,只留 toggle 一直可点;按钮符号根据状态翻向
function applyLeftCollapsed(collapsed) {
document.body.classList.toggle("left-collapsed", collapsed);
const btn = $("pane-toggle-left");
btn.textContent = collapsed ? "" : "";
btn.title = collapsed ? "展开任务列表" : "折叠任务列表";
}
function applyRightCollapsed(collapsed) {
document.body.classList.toggle("right-collapsed", collapsed);
const btn = $("pane-toggle-right");
btn.textContent = collapsed ? "" : "";
btn.title = collapsed ? "展开文件列表" : "折叠文件列表";
}
$("pane-toggle-left").onclick = () => {
const next = !document.body.classList.contains("left-collapsed");
localStorage.setItem(LS_LEFT_COLLAPSED, next ? "1" : "");
applyLeftCollapsed(next);
};
$("pane-toggle-right").onclick = () => {
const next = !document.body.classList.contains("right-collapsed");
localStorage.setItem(LS_RIGHT_COLLAPSED, next ? "1" : "");
applyRightCollapsed(next);
};
restorePaneWidths();
applyLeftCollapsed(localStorage.getItem(LS_LEFT_COLLAPSED) === "1");
applyRightCollapsed(localStorage.getItem(LS_RIGHT_COLLAPSED) === "1");
function attachPaneSplitter(id, side) {
const el = $(id);
let dragging = false;
el.addEventListener("pointerdown", (ev) => {
if (mqPhone.matches) return;
ev.preventDefault();
dragging = true;
el.classList.add("active");
document.body.classList.add("resizing-panes");
el.setPointerCapture(ev.pointerId);
if (side === "left" && document.body.classList.contains("left-collapsed")) {
localStorage.setItem(LS_LEFT_COLLAPSED, "");
applyLeftCollapsed(false);
}
if (side === "right" && document.body.classList.contains("right-collapsed")) {
localStorage.setItem(LS_RIGHT_COLLAPSED, "");
applyRightCollapsed(false);
}
});
el.addEventListener("pointermove", (ev) => {
if (!dragging) return;
const rect = $("app").getBoundingClientRect();
const width = side === "left" ? ev.clientX - rect.left : rect.right - ev.clientX;
applyPaneWidth(side, width);
});
const stop = (ev) => {
if (!dragging) return;
dragging = false;
el.classList.remove("active");
document.body.classList.remove("resizing-panes");
try { el.releasePointerCapture(ev.pointerId); } catch (_) {}
};
el.addEventListener("pointerup", stop);
el.addEventListener("pointercancel", stop);
}
attachPaneSplitter("split-left", "left");
attachPaneSplitter("split-right", "right");
// ───── 手机视图切换(单列 + tab) ─────
// body.mv-{left,mid,right} 控制当前显示的 pane;桌面下三 pane 都可见,本函数仅维护 class
// 进入手机视口时清掉 collapsed(只 DOM,不动 localStorage —— 回桌面用户偏好仍生效)
export const mqPhone = window.matchMedia("(max-width: 640px)");
export function setMobileView(view) {
// view ∈ "mv-left" | "mv-mid" | "mv-right"
document.body.classList.remove("mv-left", "mv-mid", "mv-right");
document.body.classList.add(view);
for (const b of document.querySelectorAll(".mobile-tabs button")) {
b.classList.toggle("active", b.dataset.mv === view);
}
}
function applyMobileMode() {
if (mqPhone.matches) {
// 手机:清掉桌面 rail 状态,默认显示任务列表(若未设过)
document.body.classList.remove("left-collapsed");
document.body.classList.remove("right-collapsed");
if (!document.body.matches(".mv-left, .mv-mid, .mv-right")) {
setMobileView("mv-left");
}
} else {
// 桌面/平板:恢复 localStorage 的 collapsed 偏好(平板靠 @media 强制 rail,不需依赖 class)
applyLeftCollapsed(localStorage.getItem(LS_LEFT_COLLAPSED) === "1");
applyRightCollapsed(localStorage.getItem(LS_RIGHT_COLLAPSED) === "1");
}
}
mqPhone.addEventListener("change", applyMobileMode);
applyMobileMode();
for (const b of document.querySelectorAll(".mobile-tabs button")) {
b.onclick = () => setMobileView(b.dataset.mv);
}