feat(devtool): 可拖动面板支持记住上次面板尺寸
This commit is contained in:
parent
62e3ae044f
commit
2e6e9a9367
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect, Children } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useResizePanel } from '../hooks/useResizePanel';
|
||||
import { SplitableSizesStorage } from '../utils/storageUtils';
|
||||
|
||||
interface SplitableProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -12,8 +13,13 @@ interface SplitableProps {
|
|||
onCollapsedChange?: (collapsed: boolean) => void;
|
||||
/** 是否显示折叠按钮 */
|
||||
collapseButton?: boolean;
|
||||
/** 每个面板的默认宽度,null 表示不设置默认宽度 */
|
||||
/** 每个面板的默认宽度,null 表示不设置默认宽度。优先级低于 memorizeSizesKey 记住的大小 */
|
||||
defaultSize?: (number | null)[];
|
||||
/**
|
||||
* 记忆面板大小的键名,设置后会自动保存面板大小到 localStorage。
|
||||
* 优先级高于 defaultSize,如果找到记住的大小且面板数量匹配,则使用记住的大小。
|
||||
*/
|
||||
memorizeSizesKey?: string;
|
||||
}
|
||||
|
||||
const Container = styled.div<{ $vertical?: boolean }>`
|
||||
|
@ -110,17 +116,31 @@ export const Splitable: React.FC<SplitableProps> = ({
|
|||
defaultCollapsed = false,
|
||||
onCollapsedChange,
|
||||
collapseButton = false,
|
||||
defaultSize
|
||||
defaultSize,
|
||||
memorizeSizesKey
|
||||
}) => {
|
||||
const childrenArray = Children.toArray(children);
|
||||
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
||||
const [panelSizes, setPanelSizes] = useState<number[]>(() => {
|
||||
if (memorizeSizesKey) {
|
||||
const memorizedSizes = SplitableSizesStorage.loadSizes(memorizeSizesKey);
|
||||
if (memorizedSizes && memorizedSizes.length === childrenArray.length) {
|
||||
return memorizedSizes;
|
||||
}
|
||||
}
|
||||
if (defaultSize) {
|
||||
return defaultSize.map(size => size ?? 400);
|
||||
}
|
||||
return Array(childrenArray.length).fill(400);
|
||||
});
|
||||
|
||||
// 当面板大小改变时保存到 localStorage
|
||||
useEffect(() => {
|
||||
if (memorizeSizesKey) {
|
||||
SplitableSizesStorage.saveSizes(memorizeSizesKey, panelSizes);
|
||||
}
|
||||
}, [memorizeSizesKey, panelSizes]);
|
||||
|
||||
// 为每个可调整大小的面板创建一个 useResizePanel 实例
|
||||
const resizePanels = childrenArray.map((_, index) => {
|
||||
if (index === 0) return null; // 第一个面板不需要调整大小
|
||||
|
|
|
@ -1470,6 +1470,7 @@ function VSToolBarDemo(): JSX.Element {
|
|||
function SplitableDemo(): JSX.Element {
|
||||
const [direction, setDirection] = useState<'horizontal' | 'vertical'>('horizontal');
|
||||
const [panelCount, setPanelCount] = useState(2);
|
||||
const [useMemory, setUseMemory] = useState(true);
|
||||
|
||||
const panels = Array.from({ length: panelCount }, (_, i) => (
|
||||
<div
|
||||
|
@ -1502,6 +1503,9 @@ function SplitableDemo(): JSX.Element {
|
|||
<Button onClick={() => setPanelCount(c => Math.max(c - 1, 2))}>
|
||||
移除面板
|
||||
</Button>
|
||||
<Button onClick={() => setUseMemory(m => !m)}>
|
||||
{useMemory ? '禁用大小记忆' : '启用大小记忆'}
|
||||
</Button>
|
||||
</ControlPanel>
|
||||
<div style={{
|
||||
height: '500px',
|
||||
|
@ -1509,7 +1513,10 @@ function SplitableDemo(): JSX.Element {
|
|||
borderRadius: '4px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Splitable vertical={direction === 'vertical'}>
|
||||
<Splitable
|
||||
vertical={direction === 'vertical'}
|
||||
memorizeSizesKey={useMemory ? `demo-splitable-${direction}-${panelCount}` : undefined}
|
||||
>
|
||||
{panels}
|
||||
</Splitable>
|
||||
</div>
|
||||
|
@ -1526,6 +1533,13 @@ function SplitableDemo(): JSX.Element {
|
|||
<li>最后一个面板可以通过右上角(或底部)的按钮折叠/展开</li>
|
||||
<li>拖动分隔条时会显示蓝色的指示器</li>
|
||||
<li>面板内容会自动适应容器大小</li>
|
||||
<li>大小记忆功能:</li>
|
||||
<ul>
|
||||
<li>启用后,会自动记住每个面板的大小</li>
|
||||
<li>记忆是基于方向和面板数量的,所以切换方向或改变面板数量会使用不同的记忆</li>
|
||||
<li>可以通过"禁用大小记忆"按钮来关闭此功能</li>
|
||||
<li>记忆的大小会保存在浏览器的 localStorage 中</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -693,7 +693,7 @@ const ScriptRecorder: React.FC = () => {
|
|||
/>
|
||||
)}
|
||||
<div css={css`height: 100%; margin-top: 0;`}>
|
||||
<Splitable>
|
||||
<Splitable memorizeSizesKey='ScriptRecorder.ImageViewer'>
|
||||
<ImageViewerWrapper>
|
||||
<ImageEditor
|
||||
enableMask
|
||||
|
@ -710,7 +710,7 @@ const ScriptRecorder: React.FC = () => {
|
|||
code={code}
|
||||
client={client}
|
||||
/>
|
||||
<Splitable vertical defaultSize={[null, 200]}>
|
||||
<Splitable vertical defaultSize={[null, 200]} memorizeSizesKey='ScriptRecorder.CodeEditor'>
|
||||
<AceEditor
|
||||
ref={editorRef}
|
||||
mode="python"
|
||||
|
|
|
@ -89,3 +89,30 @@ export class ScriptRecorderStorage {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SplitableSizesStorage {
|
||||
private static readonly STORAGE_KEY = 'splitable';
|
||||
|
||||
static saveSizes(key: string, sizes: number[]): void {
|
||||
const data = this.loadAllSizes();
|
||||
data[key] = sizes;
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
|
||||
}
|
||||
|
||||
static loadSizes(key: string): number[] | null {
|
||||
const data = this.loadAllSizes();
|
||||
return data[key] || null;
|
||||
}
|
||||
|
||||
private static loadAllSizes(): Record<string, number[]> {
|
||||
const dataStr = localStorage.getItem(this.STORAGE_KEY);
|
||||
if (!dataStr) return {};
|
||||
|
||||
try {
|
||||
return JSON.parse(dataStr);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse splitable sizes data:', e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue