feat(devtool): 可拖动面板支持记住上次面板尺寸

This commit is contained in:
XcantloadX 2025-02-05 10:39:08 +08:00
parent 62e3ae044f
commit 2e6e9a9367
4 changed files with 66 additions and 5 deletions

View File

@ -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; // 第一个面板不需要调整大小

View File

@ -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>

View File

@ -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"

View File

@ -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 {};
}
}
}