59 lines
4.1 KiB
TypeScript
59 lines
4.1 KiB
TypeScript
import { useRef, useState, type ReactNode } from 'react';
|
|
import { Columns2, Maximize2 } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import './desktop-workspace.css';
|
|
|
|
const preferenceKey = 'makelore.cloud-agent-editor-ratio';
|
|
|
|
export function DesktopSplit({ configuration, preview }: { configuration: ReactNode; preview: (action: ReactNode) => ReactNode }) {
|
|
const root = useRef<HTMLDivElement>(null);
|
|
const [ratio, setRatio] = useState(() => {
|
|
try { const saved = Number(localStorage.getItem(preferenceKey)); return saved >= 25 && saved <= 75 ? saved : 46; }
|
|
catch { return 46; }
|
|
});
|
|
const [focus, setFocus] = useState<'configuration' | 'preview' | null>(null);
|
|
const [compactPanel, setCompactPanel] = useState<'configuration' | 'preview'>('configuration');
|
|
const resize = (next: number) => {
|
|
const width = (root.current?.clientWidth ?? 0) - 8;
|
|
if (width < 720) return;
|
|
const clamped = Math.max(25, 340 / width * 100, Math.min(75, 100 - 380 / width * 100, next));
|
|
setRatio(clamped);
|
|
try { localStorage.setItem(preferenceKey, String(clamped)); } catch { /* Layout remains usable without persistence. */ }
|
|
};
|
|
const panelToggle = (panel: 'configuration' | 'preview') => <Button variant="ghost" size="icon" className="h-10 w-10"
|
|
aria-label={focus === panel ? '恢复分栏' : panel === 'configuration' ? '展开配置' : '展开试用'}
|
|
onClick={() => setFocus(focus === panel ? null : panel)}>{focus === panel ? <Columns2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}</Button>;
|
|
return <div className="agent-split-container" onKeyDown={e => {
|
|
if (e.key === 'Escape') setFocus(null);
|
|
if (e.key === 'F6' && root.current) {
|
|
const panels = [...root.current.querySelectorAll<HTMLElement>('.agent-config-pane, .agent-preview-pane')].filter(panel => panel.offsetParent !== null);
|
|
const current = panels.findIndex(panel => panel.contains(document.activeElement));
|
|
const next = panels[(current + 1) % panels.length];
|
|
const target = [...(next?.querySelectorAll<HTMLElement>('textarea:not(:disabled), input:not(:disabled), button:not(:disabled)') ?? [])].find(el => el.offsetParent !== null);
|
|
if (target) { e.preventDefault(); target.focus(); }
|
|
}
|
|
}}>
|
|
<div className="agent-compact-switch" aria-label="工作区面板">
|
|
<Button variant={compactPanel === 'configuration' ? 'secondary' : 'ghost'} onClick={() => { setFocus(null); setCompactPanel('configuration'); }}>配置</Button>
|
|
<Button variant={compactPanel === 'preview' ? 'secondary' : 'ghost'} onClick={() => { setFocus(null); setCompactPanel('preview'); }}>试用</Button>
|
|
</div>
|
|
<div ref={root} className="agent-split" data-focus={focus ?? ''} data-compact-panel={compactPanel}
|
|
style={{ gridTemplateColumns: focus ? 'minmax(0, 1fr)' : `minmax(340px, ${ratio}fr) 8px minmax(380px, ${100 - ratio}fr)` }}>
|
|
<section className="agent-config-pane" aria-label="草稿配置">
|
|
<div className="agent-pane-heading"><span>配置智能体</span>{panelToggle('configuration')}</div>
|
|
{configuration}
|
|
</section>
|
|
<div role="separator" aria-label="调整配置与试用宽度" aria-orientation="vertical" tabIndex={0}
|
|
aria-valuemin={25} aria-valuemax={75} aria-valuenow={Math.round(ratio)} className="agent-divider"
|
|
onDoubleClick={() => resize(46)}
|
|
onPointerDown={e => { e.preventDefault(); e.currentTarget.focus(); e.currentTarget.setPointerCapture(e.pointerId); }}
|
|
onPointerMove={e => { if (e.currentTarget.hasPointerCapture(e.pointerId) && root.current) resize((e.clientX - root.current.getBoundingClientRect().left) / (root.current.clientWidth - 8) * 100); }}
|
|
onPointerUp={e => e.currentTarget.releasePointerCapture(e.pointerId)}
|
|
onKeyDown={e => { if (['ArrowLeft', 'ArrowRight', 'Home'].includes(e.key)) { e.preventDefault(); resize(e.key === 'Home' ? 46 : ratio + (e.key === 'ArrowLeft' ? -2 : 2)); } }} />
|
|
<section className="agent-preview-pane" aria-label="试用工作区">
|
|
{preview(panelToggle('preview'))}
|
|
</section>
|
|
</div>
|
|
</div>;
|
|
}
|