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