桌面 GUI 外链修复 + 主题顺序调整(含结构扁平化进行中的工作区基线)
- 集成 tauri-plugin-opener:Cargo/lib.rs/capabilities/npm 四处接线,新增 src/openExternal.ts(桌面走 opener,浏览器回退 window.open,相对路径绝对化) - 关于页 4 个链接、活动栏官网菜单、代理错误横幅 2 处健康检查链接全部改走 openExternal,桌面端可正常跳转 - 设置→外观 主题顺序调整为 深色/浅色/跟随系统 - release 二进制重编并验证(浏览器实测关于页链接可开文档页) - 附带工作区中未提交的扁平化重构现状(另一会话进行中) - homepage 落地页与文档终检通过(8787 / 与 /docs 双 200)
This commit is contained in:
1 parent
6452026fd6
commit
2bb4ef7a95
299 files changed
+6059
-3322
No files matched your search
@@ -0,0 +1,124 @@
|
||||
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
|
||||
|
||||
type Props = {
|
||||
direction?: "horizontal" | "vertical";
|
||||
initialRatio?: number;
|
||||
storageKey?: string;
|
||||
minPx?: number;
|
||||
minSecondPx?: number;
|
||||
className?: string;
|
||||
children: [ReactNode, ReactNode];
|
||||
};
|
||||
|
||||
function clamp(n: number, lo: number, hi: number) {
|
||||
return Math.min(hi, Math.max(lo, n));
|
||||
}
|
||||
|
||||
export function SplitPane({
|
||||
direction = "vertical",
|
||||
initialRatio = 0.45,
|
||||
storageKey,
|
||||
minPx = 120,
|
||||
minSecondPx = minPx,
|
||||
className = "",
|
||||
children,
|
||||
}: Props) {
|
||||
const [a, b] = children;
|
||||
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||
const [size, setSize] = useState(0);
|
||||
const [ratio, setRatio] = useState(() => {
|
||||
if (storageKey) {
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
const n = raw ? Number(raw) : NaN;
|
||||
if (Number.isFinite(n) && n > 0.1 && n < 0.9) return n;
|
||||
}
|
||||
return initialRatio;
|
||||
});
|
||||
const dragging = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const root = rootRef.current;
|
||||
if (!root) return;
|
||||
const measure = () => setSize(direction === "vertical" ? root.clientHeight : root.clientWidth);
|
||||
const observer = new ResizeObserver(measure);
|
||||
observer.observe(root);
|
||||
measure();
|
||||
return () => observer.disconnect();
|
||||
}, [direction]);
|
||||
|
||||
useEffect(() => {
|
||||
if (storageKey) localStorage.setItem(storageKey, String(ratio));
|
||||
}, [ratio, storageKey]);
|
||||
|
||||
const onPointerMove = useCallback(
|
||||
(e: PointerEvent) => {
|
||||
if (!dragging.current || !rootRef.current) return;
|
||||
const rect = rootRef.current.getBoundingClientRect();
|
||||
if (direction === "vertical") {
|
||||
const y = e.clientY - rect.top;
|
||||
if (rect.height) setRatio(clamp(y / rect.height, minPx / rect.height, 1 - (minSecondPx + 8) / rect.height));
|
||||
} else {
|
||||
const x = e.clientX - rect.left;
|
||||
if (rect.width) setRatio(clamp(x / rect.width, minPx / rect.width, 1 - (minSecondPx + 8) / rect.width));
|
||||
}
|
||||
},
|
||||
[direction, minPx, minSecondPx],
|
||||
);
|
||||
|
||||
const stop = useCallback(() => {
|
||||
dragging.current = false;
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("pointermove", onPointerMove);
|
||||
window.addEventListener("pointerup", stop);
|
||||
return () => {
|
||||
window.removeEventListener("pointermove", onPointerMove);
|
||||
window.removeEventListener("pointerup", stop);
|
||||
};
|
||||
}, [onPointerMove, stop]);
|
||||
|
||||
const vert = direction === "vertical";
|
||||
// A stored ratio can become unusable after shrinking the window. Reserve room
|
||||
// for both panes and the separator at the current measured size.
|
||||
const firstSize = size ? Math.min(Math.max(size * ratio, Math.min(minPx, size / 2)), Math.max(0, size - minSecondPx - 8)) : undefined;
|
||||
return (
|
||||
<div
|
||||
ref={rootRef}
|
||||
className={`flex min-h-0 min-w-0 flex-1 ${vert ? "flex-col" : "flex-row"} ${className}`}
|
||||
>
|
||||
<div className="flex min-h-0 min-w-0 shrink-0 flex-col overflow-hidden" style={vert ? { height: firstSize ?? `${ratio * 100}%` } : { width: firstSize ?? `${ratio * 100}%` }}>
|
||||
{a}
|
||||
</div>
|
||||
<div
|
||||
role="separator"
|
||||
aria-orientation={vert ? "horizontal" : "vertical"}
|
||||
title="拖动调整;双击复位"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault();
|
||||
dragging.current = true;
|
||||
document.body.style.cursor = vert ? "row-resize" : "col-resize";
|
||||
document.body.style.userSelect = "none";
|
||||
}}
|
||||
onDoubleClick={() => setRatio(initialRatio)}
|
||||
className={
|
||||
vert
|
||||
? "group relative z-10 h-2 shrink-0 cursor-row-resize"
|
||||
: "group relative z-10 w-2 shrink-0 cursor-col-resize"
|
||||
}
|
||||
style={{ background: "transparent" }}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
vert
|
||||
? "absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-[var(--border)] transition group-hover:h-1 group-hover:bg-[var(--accent)]"
|
||||
: "absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-[var(--border)] transition group-hover:w-1 group-hover:bg-[var(--accent)]"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">{b}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user