feat(web-platform): release V0.9.3 全模块界面重构
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled

This commit is contained in:
2026-09-08 17:57:57 +08:00
parent 831d0b95bb
commit 13e35be98b
124 changed files with 5182 additions and 2141 deletions
@@ -1,3 +1,4 @@
import { FloatingLayerContext, useFloatingLayer } from '../../components/ui/floating';
import './monacoSetup';
import Editor from '@monaco-editor/react';
import {
@@ -42,6 +43,7 @@ export function SourceEditorDialog({
const [code, setCode] = useState(sourceCode),
[savedCode, setSavedCode] = useState(sourceCode),
[saving, setSaving] = useState(false),
[saveError, setSaveError] = useState<string>(),
[copied, setCopied] = useState(false),
[maximized, setMaximized] = useState(false),
[discardOpen, setDiscardOpen] = useState(false),
@@ -61,13 +63,17 @@ export function SourceEditorDialog({
const save = useCallback(async () => {
if (!dirty || problem) return;
setSaving(true);
setSaveError(undefined);
try {
await onSave(filePath, code);
setSavedCode(code);
} catch (value) {
setSaveError(value instanceof Error ? value.message : String(value));
} finally {
setSaving(false);
}
}, [code, dirty, filePath, onSave, problem]);
const layer = useFloatingLayer({ open, roots: () => [dialog.current], dismiss: requestClose });
useEffect(() => {
if (!open) return;
previousFocus.current =
@@ -80,7 +86,7 @@ export function SourceEditorDialog({
}, [open]);
useEffect(() => {
const key = (event: KeyboardEvent) => {
if (discardOpen) return;
if (!open || discardOpen || !dialog.current?.contains(document.activeElement)) return;
if (
(event.ctrlKey || event.metaKey) &&
event.key.toLowerCase() === 's' &&
@@ -89,14 +95,11 @@ export function SourceEditorDialog({
) {
event.preventDefault();
void save();
} else if (event.key === 'Escape') {
event.preventDefault();
requestClose();
}
};
window.addEventListener('keydown', key);
return () => window.removeEventListener('keydown', key);
}, [dirty, discardOpen, problem, requestClose, save]);
}, [open, dirty, discardOpen, problem, requestClose, save]);
const copy = async () => {
await navigator.clipboard.writeText(code);
setCopied(true);
@@ -124,7 +127,7 @@ export function SourceEditorDialog({
};
if (!open) return null;
return (
<>
<FloatingLayerContext.Provider value={layer.context}>
<div className="fixed inset-0 z-[390] pointer-events-none" role="presentation">
<section
ref={dialog}
@@ -133,12 +136,20 @@ export function SourceEditorDialog({
aria-modal="false"
aria-label="转换后的 MJCF 编辑器"
style={
maximized ? undefined : { left: position.x, top: position.y, width: 900, height: 650 }
maximized
? { zIndex: layer.zIndex }
: {
zIndex: layer.zIndex,
left: `clamp(16px, ${position.x}px, max(16px, calc(100vw - 916px)))`,
top: `clamp(16px, ${position.y}px, max(16px, calc(100vh - 666px)))`,
width: 'min(900px, calc(100vw - 32px))',
height: 'min(650px, calc(100vh - 32px))',
}
}
className={`source-editor-window pointer-events-auto fixed flex min-h-[360px] min-w-[520px] flex-col overflow-hidden border border-border-strong bg-panel shadow-2xl ${maximized ? 'inset-0 h-full w-full' : 'resize'}`}
className={`source-editor-window pointer-events-auto fixed flex min-h-64 min-w-0 max-w-[calc(100vw-32px)] max-h-[calc(100vh-32px)] flex-col overflow-hidden border border-border-strong bg-panel shadow-2xl ${maximized ? 'inset-0 h-full w-full' : 'resize'}`}
>
<header
className="flex h-11 shrink-0 cursor-move select-none items-center gap-3 border-b border-border bg-surface px-3"
className="flex min-h-11 flex-wrap shrink-0 py-2 cursor-move select-none items-center gap-3 border-b border-border bg-surface px-3"
onPointerDown={pointerDown}
onPointerMove={pointerMove}
onPointerUp={() => {
@@ -151,16 +162,16 @@ export function SourceEditorDialog({
<div className="truncate font-mono text-xs font-semibold text-text-primary">
转换后的 MJCF
</div>
<div className="truncate font-mono text-[9px] text-text-tertiary" title={filePath}>
<div className="break-all font-mono text-xs text-text-tertiary" title={filePath}>
{filePath}
</div>
</div>
<span className="text-[10px] text-text-tertiary">{contentSize(code)}</span>
<span className="rounded bg-accent-soft px-1.5 py-0.5 text-[9px] font-semibold text-accent">
<span className="text-xs text-text-tertiary">{contentSize(code)}</span>
<span className="rounded bg-accent-soft px-1.5 py-0.5 text-xs font-semibold text-accent">
缓存文件 · 可编辑
</span>
{dirty && (
<span className="rounded bg-warning-soft px-1.5 py-0.5 text-[9px] font-semibold text-warning">
<span className="rounded bg-warning-soft px-1.5 py-0.5 text-xs font-semibold text-warning">
已修改
</span>
)}
@@ -193,6 +204,14 @@ export function SourceEditorDialog({
<X className="h-4 w-4" />
</IconButton>
</header>
<p className="border-b border-border px-3 py-2 text-xs text-warning">
保存将重新编译并载入模型;未保存的源码关闭后无法恢复。
</p>
{saveError && (
<p role="alert" className="px-3 py-2 text-xs text-danger">
{saveError}
</p>
)}
<div className="min-h-0 flex-1 bg-input">
<Editor
height="100%"
@@ -218,8 +237,8 @@ export function SourceEditorDialog({
}}
/>
</div>
<footer className="flex h-7 shrink-0 items-center justify-between gap-3 border-t border-border bg-surface px-3 text-[10px]">
<div className={problem ? 'truncate text-warning' : 'text-success'}>
<footer className="flex min-h-7 flex-wrap shrink-0 items-center justify-between gap-3 border-t border-border bg-surface px-3 text-xs">
<div className={problem ? 'break-words text-warning' : 'text-success'}>
{problem ? `XML 错误:${problem}` : '✓ XML 结构正常'}
</div>
<div className="flex items-center gap-2 font-mono text-text-tertiary">
@@ -243,6 +262,6 @@ export function SourceEditorDialog({
当前 MJCF 源码包含未保存的修改。关闭后,这些修改将无法恢复。
</p>
</ConfirmDialog>
</>
</FloatingLayerContext.Provider>
);
}