Files
makelore/src/pages/CloudAgents/DesktopSplit.tsx
T

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