121 lines
5.2 KiB
JavaScript
121 lines
5.2 KiB
JavaScript
// 三栏布局: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);
|
||
}
|