// 三栏布局: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); }