210 lines
6.7 KiB
TypeScript
210 lines
6.7 KiB
TypeScript
import type { ChangeEvent, ReactNode } from 'react';
|
||
import {
|
||
CircleHelp,
|
||
Code2,
|
||
Expand,
|
||
FolderOpen,
|
||
Minimize,
|
||
PanelLeft,
|
||
PanelRight,
|
||
Pause,
|
||
Play,
|
||
RotateCcw,
|
||
Search,
|
||
StepForward,
|
||
Sun,
|
||
Moon,
|
||
Upload,
|
||
} from 'lucide-react';
|
||
import { Button, IconButton, Select } from '../../components/ui';
|
||
const fileActionClass =
|
||
'inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-surface px-2 text-xs font-medium text-text-primary transition-colors hover:bg-element-hover focus-within:ring-2 focus-within:ring-accent/30';
|
||
const fileActionLabelClass = 'hidden sm:inline';
|
||
export function WorkbenchHeader({
|
||
paused,
|
||
ready,
|
||
speed,
|
||
theme,
|
||
loading,
|
||
leftOpen,
|
||
rightOpen,
|
||
fullscreen,
|
||
hasProject,
|
||
center,
|
||
endActions,
|
||
compactMenu,
|
||
onFiles,
|
||
onFolder,
|
||
onOpenSource,
|
||
onTogglePause,
|
||
onStep,
|
||
onReset,
|
||
onSpeed,
|
||
onToggleLeft,
|
||
onToggleRight,
|
||
onToggleTheme,
|
||
onHelp,
|
||
onCommands,
|
||
onToggleFullscreen,
|
||
}: {
|
||
paused: boolean;
|
||
ready: boolean;
|
||
speed: number;
|
||
theme: 'light' | 'dark';
|
||
loading: boolean;
|
||
leftOpen: boolean;
|
||
rightOpen: boolean;
|
||
fullscreen: boolean;
|
||
hasProject?: boolean;
|
||
center: ReactNode;
|
||
endActions?: ReactNode;
|
||
compactMenu?: ReactNode;
|
||
onFiles: (event: ChangeEvent<HTMLInputElement>) => void;
|
||
onFolder: (event: ChangeEvent<HTMLInputElement>) => void;
|
||
onOpenSource?: () => void;
|
||
onTogglePause: () => void;
|
||
onStep: () => void;
|
||
onReset: () => void;
|
||
onSpeed: (value: number) => void;
|
||
onToggleLeft: () => void;
|
||
onToggleRight: () => void;
|
||
onToggleTheme: () => void;
|
||
onHelp: () => void;
|
||
onCommands: () => void;
|
||
onToggleFullscreen: () => void;
|
||
}) {
|
||
return (
|
||
<header className="relative z-40 grid h-10 shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(max-content,1fr)] items-center gap-2 border-b border-border bg-panel px-2.5">
|
||
<div className="flex min-w-0 items-center gap-1">
|
||
<h1 className="mr-2 hidden truncate border-r border-border pr-3 text-sm font-semibold text-text-primary xl:block">
|
||
MuJoCo Web 仿真平台
|
||
</h1>
|
||
<label
|
||
aria-disabled={loading}
|
||
className={`${fileActionClass} ${loading ? 'pointer-events-none opacity-40' : ''}`}
|
||
>
|
||
<Upload className="h-3.5 w-3.5" />
|
||
<span className={fileActionLabelClass}>打开文件</span>
|
||
<input
|
||
id="mujoco-project-files"
|
||
aria-label="打开文件"
|
||
className="sr-only"
|
||
type="file"
|
||
disabled={loading}
|
||
multiple
|
||
accept=".xml,.urdf,.zip,.obj,.stl,.dae,.msh,.png,.jpg,.jpeg,.bmp,.tga,.hdr"
|
||
onChange={onFiles}
|
||
/>
|
||
</label>
|
||
<label
|
||
aria-disabled={loading}
|
||
className={`${fileActionClass} ${loading ? 'pointer-events-none opacity-40' : ''}`}
|
||
>
|
||
<FolderOpen className="h-3.5 w-3.5" />
|
||
<span className={fileActionLabelClass}>文件夹</span>
|
||
<input
|
||
id="mujoco-project-folder"
|
||
aria-label="打开文件夹"
|
||
className="sr-only"
|
||
type="file"
|
||
disabled={loading}
|
||
multiple
|
||
{...({ webkitdirectory: '', directory: '' } as object)}
|
||
onChange={onFolder}
|
||
/>
|
||
</label>
|
||
<IconButton
|
||
tooltip="查看和修改缓存源代码"
|
||
aria-label="源代码"
|
||
disabled={!hasProject || loading}
|
||
onClick={onOpenSource}
|
||
>
|
||
<Code2 className="h-4 w-4" />
|
||
</IconButton>
|
||
</div>
|
||
<div className="flex items-center justify-center">{center}</div>
|
||
<div className="flex min-w-0 items-center justify-end gap-0.5">
|
||
<Button
|
||
variant="ghost"
|
||
onClick={onTogglePause}
|
||
disabled={!ready}
|
||
aria-label={paused ? '▶ 播放' : '⏸ 暂停'}
|
||
icon={paused ? <Play className="h-3.5 w-3.5" /> : <Pause className="h-3.5 w-3.5" />}
|
||
>
|
||
{paused ? '播放' : '暂停'}
|
||
</Button>
|
||
<IconButton tooltip="单步" aria-label="单步" onClick={onStep} disabled={!ready || !paused}>
|
||
<StepForward className="h-3.5 w-3.5" />
|
||
</IconButton>
|
||
<IconButton tooltip="重置" aria-label="重置" onClick={onReset} disabled={!ready}>
|
||
<RotateCcw className="h-3.5 w-3.5" />
|
||
</IconButton>
|
||
<Select
|
||
aria-label="仿真速度"
|
||
value={speed}
|
||
disabled={loading}
|
||
onChange={(event) => onSpeed(Number(event.target.value))}
|
||
className="w-[70px]"
|
||
>
|
||
<option value={0.25}>0.25×</option>
|
||
<option value={0.5}>0.5×</option>
|
||
<option value={1}>1×</option>
|
||
<option value={2}>2×</option>
|
||
<option value={4}>4×</option>
|
||
</Select>
|
||
<span className="mx-1 h-5 border-l border-border" />
|
||
<IconButton
|
||
tooltip={leftOpen ? '隐藏工程面板' : '显示工程面板'}
|
||
aria-label={leftOpen ? '隐藏工程面板' : '显示工程面板'}
|
||
aria-expanded={leftOpen}
|
||
onClick={onToggleLeft}
|
||
>
|
||
<PanelLeft className="h-4 w-4" />
|
||
</IconButton>
|
||
<IconButton
|
||
tooltip={rightOpen ? '隐藏属性面板' : '显示属性面板'}
|
||
aria-label={rightOpen ? '隐藏属性面板' : '显示属性面板'}
|
||
aria-expanded={rightOpen}
|
||
onClick={onToggleRight}
|
||
>
|
||
<PanelRight className="h-4 w-4" />
|
||
</IconButton>
|
||
{endActions}
|
||
{compactMenu}
|
||
<IconButton
|
||
className="hidden xl:inline-flex"
|
||
tooltip="命令面板(Ctrl+K)"
|
||
aria-label="打开命令面板"
|
||
onClick={onCommands}
|
||
>
|
||
<Search className="h-4 w-4" />
|
||
</IconButton>
|
||
<IconButton
|
||
className="hidden xl:inline-flex"
|
||
tooltip={fullscreen ? '退出全屏' : '进入全屏'}
|
||
aria-label={fullscreen ? '退出全屏' : '进入全屏'}
|
||
onClick={onToggleFullscreen}
|
||
>
|
||
{fullscreen ? <Minimize className="h-4 w-4" /> : <Expand className="h-4 w-4" />}
|
||
</IconButton>
|
||
<IconButton
|
||
className="hidden xl:inline-flex"
|
||
tooltip="快捷键帮助"
|
||
aria-label="快捷键帮助"
|
||
onClick={onHelp}
|
||
>
|
||
<CircleHelp className="h-4 w-4" />
|
||
</IconButton>
|
||
<IconButton
|
||
className="hidden xl:inline-flex"
|
||
tooltip={theme === 'dark' ? '切换到白天主题' : '切换到黑夜主题'}
|
||
aria-label={theme === 'dark' ? '切换到白天主题' : '切换到黑夜主题'}
|
||
onClick={onToggleTheme}
|
||
>
|
||
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
||
</IconButton>
|
||
</div>
|
||
</header>
|
||
);
|
||
}
|