feat(web-platform): release V0.9.4 前端设计优化
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
This commit is contained in:
@@ -27,7 +27,12 @@ describe('第二批工作台组件', () => {
|
||||
it('空工作区解释导入到仿真的三步流程', () => {
|
||||
render(<EmptyWorkspace />);
|
||||
expect(screen.getByRole('region', { name: '导入模型工程' })).toBeVisible();
|
||||
fireEvent.click(screen.getByText('导入与仿真流程'));
|
||||
expect(screen.getByText('Local Simulation Workspace')).toBeVisible();
|
||||
expect(screen.getByText('本地解析、编译并运行机器人模型,无需上传资源')).toBeVisible();
|
||||
expect(screen.getByLabelText('支持格式')).toHaveTextContent('OBJ / STL / DAE');
|
||||
expect(screen.getByRole('button', { name: '选择文件' })).toBeVisible();
|
||||
expect(screen.getByRole('button', { name: '选择文件夹' })).toBeVisible();
|
||||
expect(screen.queryByText('导入与仿真流程')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('list', { name: '仿真工作流程' })).toHaveTextContent('导入');
|
||||
expect(screen.getByRole('list', { name: '仿真工作流程' })).toHaveTextContent('检查与配置');
|
||||
expect(screen.getByRole('list', { name: '仿真工作流程' })).toHaveTextContent('运行与调试');
|
||||
|
||||
@@ -18,7 +18,7 @@ export function SidebarPanel({
|
||||
return (
|
||||
<ResizablePanel side={side} storageKey={`mujoco-${side}-sidebar-width`} visible={visible}>
|
||||
<aside
|
||||
className={`flex h-full w-full min-w-0 flex-col overflow-hidden bg-panel ${side === 'left' ? 'border-r' : 'border-l'} border-border-subtle `}
|
||||
className={`cyber-sidebar flex h-full w-full min-w-0 flex-col overflow-hidden ${side === 'left' ? 'border-r' : 'border-l'} border-border-subtle `}
|
||||
>
|
||||
<h2 className="flex h-9 shrink-0 items-center gap-2 border-b border-border-subtle px-3 text-xs font-semibold text-text-primary">
|
||||
<span className="text-accent [&>svg]:h-4 [&>svg]:w-4">
|
||||
|
||||
@@ -146,7 +146,7 @@ export function SourceEditorDialog({
|
||||
height: 'min(650px, calc(100vh - 32px))',
|
||||
}
|
||||
}
|
||||
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'}`}
|
||||
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 ${maximized ? 'inset-0 h-full w-full' : 'resize'}`}
|
||||
>
|
||||
<header
|
||||
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"
|
||||
|
||||
@@ -9,9 +9,10 @@ export function ViewportHUD() {
|
||||
return (
|
||||
<div
|
||||
aria-label="视口状态"
|
||||
className="flex flex-wrap items-center gap-2 rounded-lg border border-border bg-panel/90 px-2 py-1 text-xs shadow-sm"
|
||||
className="engineering-glass flex flex-wrap items-center gap-2 rounded-lg border border-border px-2 py-1 text-xs"
|
||||
>
|
||||
<Badge tone={!ready || paused ? 'neutral' : 'success'}>
|
||||
<span aria-hidden="true" className="hud-signal" data-running={ready && !paused} />
|
||||
{!ready || paused ? (
|
||||
<CirclePause className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
|
||||
@@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { WorkbenchHeader } from './WorkbenchHeader';
|
||||
const fn = () => {};
|
||||
describe('WorkbenchHeader', () => {
|
||||
it('保留导入、工程菜单、命令与侧栏入口,不再重复仿真控制', () => {
|
||||
it('保留导入、源代码直达、命令与侧栏入口,不再重复仿真控制', () => {
|
||||
const source = vi.fn(),
|
||||
files = vi.fn(),
|
||||
left = vi.fn();
|
||||
@@ -26,8 +26,8 @@ describe('WorkbenchHeader', () => {
|
||||
});
|
||||
expect(files).toHaveBeenCalledOnce();
|
||||
expect(screen.getByLabelText('打开文件夹')).toHaveAttribute('webkitdirectory');
|
||||
expect(screen.queryByRole('button', { name: '源代码' })).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '工程' }));
|
||||
expect(screen.getByRole('button', { name: '源代码' })).toBeVisible();
|
||||
expect(screen.queryByRole('button', { name: '工程' })).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '源代码' }));
|
||||
expect(source).toHaveBeenCalledOnce();
|
||||
fireEvent.click(screen.getByRole('button', { name: '隐藏工程面板' }));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ChangeEvent, ReactNode } from 'react';
|
||||
import { Boxes, Code2, FolderOpen, PanelLeft, PanelRight, Search, Upload } from 'lucide-react';
|
||||
import { IconButton, Popover } from '../../components/ui';
|
||||
import { IconButton } from '../../components/ui';
|
||||
const fileActionClass =
|
||||
'inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-surface/80 px-2 text-xs font-medium text-text-primary shadow-sm transition-[background-color,border-color,transform] hover:-translate-y-px hover:border-border-strong hover:bg-element-hover focus-within:ring-2 focus-within:ring-accent/30';
|
||||
'cyber-action inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-surface/80 px-2 text-xs font-medium text-text-primary shadow-sm transition-[background-color,border-color,transform] hover:-translate-y-px hover:border-border-strong hover:bg-element-hover focus-within:ring-2 focus-within:ring-accent/30';
|
||||
const fileActionLabelClass = 'whitespace-nowrap';
|
||||
export function WorkbenchHeader({
|
||||
loading,
|
||||
@@ -32,7 +32,7 @@ export function WorkbenchHeader({
|
||||
onCommands: () => void;
|
||||
}) {
|
||||
return (
|
||||
<header className="workbench-header relative z-40 flex h-11 shrink-0 justify-between items-center gap-2 border-b border-border px-2.5 shadow-[0_1px_0_rgba(255,255,255,0.02)] backdrop-blur-md">
|
||||
<header className="workbench-header relative z-40 flex h-11 shrink-0 justify-between items-center gap-2 border-b border-border px-2.5 ">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
<div className="mr-2 flex items-center gap-2">
|
||||
<span className="grid h-7 w-7 place-items-center rounded-lg border border-accent/25 bg-accent-soft text-accent shadow-sm">
|
||||
@@ -76,32 +76,15 @@ export function WorkbenchHeader({
|
||||
onChange={onFolder}
|
||||
/>
|
||||
</label>
|
||||
<Popover
|
||||
label="工程菜单"
|
||||
placement="bottom-left"
|
||||
trigger={({ toggle }) => (
|
||||
<button type="button" className={fileActionClass} onClick={toggle}>
|
||||
工程
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className={`${fileActionClass} disabled:pointer-events-none disabled:opacity-40`}
|
||||
disabled={!hasProject || loading}
|
||||
onClick={onOpenSource}
|
||||
>
|
||||
{({ close }) => (
|
||||
<div className="w-52 rounded-lg border border-border bg-surface-elevated p-1 shadow-xl">
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-8 w-full items-center gap-2 rounded px-2 text-xs hover:bg-element-hover disabled:opacity-40"
|
||||
disabled={!hasProject || loading}
|
||||
onClick={() => {
|
||||
close();
|
||||
onOpenSource?.();
|
||||
}}
|
||||
>
|
||||
<Code2 className="h-4 w-4" />
|
||||
源代码
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
<Code2 aria-hidden="true" className="h-3.5 w-3.5" />
|
||||
<span className={fileActionLabelClass}>源代码</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex min-w-0 items-center justify-end gap-0.5">
|
||||
<IconButton
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
LoaderCircle,
|
||||
PlayCircle,
|
||||
Settings2,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
Upload,
|
||||
UploadCloud,
|
||||
} from 'lucide-react';
|
||||
@@ -39,36 +41,78 @@ export function EmptyWorkspace({ compact = false }: { compact?: boolean }) {
|
||||
return (
|
||||
<section
|
||||
aria-label="导入模型工程"
|
||||
className="mx-auto w-full max-w-md rounded-lg border border-border bg-panel p-6 text-center"
|
||||
className="workspace-welcome relative w-full max-w-[600px] overflow-hidden rounded-[22px] border border-border-strong px-6 py-6 text-center sm:px-7"
|
||||
>
|
||||
<h2 className="text-base font-semibold">拖放模型工程到此处</h2>
|
||||
<p className="mt-2 text-xs text-text-tertiary">支持 MJCF/XML、URDF、文件夹和 ZIP</p>
|
||||
<div className="mt-4 flex flex-wrap justify-center gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={<Upload className="h-4 w-4" />}
|
||||
onClick={() => document.getElementById('mujoco-project-files')?.click()}
|
||||
<div aria-hidden="true" className="welcome-glow" />
|
||||
<div className="relative">
|
||||
<div className="mb-4 flex items-center justify-center gap-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-accent">
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
Local Simulation Workspace
|
||||
</div>
|
||||
<span className="mx-auto mb-4 grid h-14 w-14 place-items-center rounded-2xl border border-accent/25 bg-accent-soft text-accent tool-active-glow">
|
||||
<UploadCloud className="h-6 w-6" />
|
||||
</span>
|
||||
<h2 className="text-lg font-semibold tracking-tight text-text-primary">
|
||||
拖放模型工程到此处
|
||||
</h2>
|
||||
<p className="mt-1.5 text-xs leading-5 text-text-tertiary">
|
||||
本地解析、编译并运行机器人模型,无需上传资源
|
||||
</p>
|
||||
<div
|
||||
className="mt-4 flex flex-wrap items-center justify-center gap-1.5"
|
||||
aria-label="支持格式"
|
||||
>
|
||||
选择文件
|
||||
</Button>
|
||||
<Button
|
||||
icon={<FolderOpen className="h-4 w-4" />}
|
||||
onClick={() => document.getElementById('mujoco-project-folder')?.click()}
|
||||
{['MJCF / XML', 'URDF', 'ZIP', 'OBJ / STL / DAE'].map((format) => (
|
||||
<span
|
||||
key={format}
|
||||
className="rounded-full border border-border bg-surface/80 px-2.5 py-1 text-[10px] font-medium text-text-secondary"
|
||||
>
|
||||
{format}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-5 flex flex-wrap items-center justify-center gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={<Upload className="h-4 w-4" />}
|
||||
onClick={() => document.getElementById('mujoco-project-files')?.click()}
|
||||
>
|
||||
选择文件
|
||||
</Button>
|
||||
<Button
|
||||
icon={<FolderOpen className="h-4 w-4" />}
|
||||
onClick={() => document.getElementById('mujoco-project-folder')?.click()}
|
||||
>
|
||||
选择文件夹
|
||||
</Button>
|
||||
</div>
|
||||
<ol
|
||||
aria-label="仿真工作流程"
|
||||
className="mt-6 hidden grid-cols-3 gap-2 border-t border-border-subtle pt-4 sm:grid"
|
||||
>
|
||||
选择文件夹
|
||||
</Button>
|
||||
</div>
|
||||
<details className="domain-details mt-4 text-left">
|
||||
<summary>导入与仿真流程</summary>
|
||||
<ol aria-label="仿真工作流程" className="space-y-2 text-xs text-text-secondary">
|
||||
{workflow.map((item) => (
|
||||
<li key={item.label}>
|
||||
{item.label} · {item.detail}
|
||||
{workflow.map((item, index) => (
|
||||
<li
|
||||
key={item.label}
|
||||
className="engineering-card rounded-xl border border-border/70 px-3 py-3 text-left"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="technical-value text-[10px] font-semibold text-accent">
|
||||
0{index + 1}
|
||||
</span>
|
||||
<item.icon aria-hidden="true" className="h-3.5 w-3.5 text-text-secondary" />
|
||||
<span className="text-xs font-semibold text-text-primary">{item.label}</span>
|
||||
</div>
|
||||
<span className="mt-1 block text-[10px] leading-4 text-text-tertiary">
|
||||
{item.detail}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</details>
|
||||
<p className="mt-3 text-xs text-text-tertiary">模型与资源仅在当前浏览器会话中处理</p>
|
||||
<p className="mt-4 flex items-center justify-center gap-1.5 text-[10px] text-text-tertiary">
|
||||
<ShieldCheck aria-hidden="true" className="h-3.5 w-3.5 text-success" />
|
||||
模型与资源仅在当前浏览器会话中处理
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,10 +135,14 @@ export function WorkspaceToolsPanel({
|
||||
keepMounted
|
||||
badge={<ConsoleSectionBadges count={snapshot.actuators.length} />}
|
||||
>
|
||||
{snapshot.actuators.some((actuator) => ['motor', 'position'].includes(actuator.kind)) && (
|
||||
<p className="mb-2 text-xs text-warning">参数立即作用于模型,可随 MJCF 导出。</p>
|
||||
)}
|
||||
{snapshot.actuators.length ? (
|
||||
snapshot.actuators.map((actuator) => (
|
||||
<ActuatorControl
|
||||
key={actuator.id}
|
||||
showParameterNotice={false}
|
||||
actuator={actuator}
|
||||
onControl={(value) => onActuator(actuator.id, value)}
|
||||
onParameters={(parameters) => onActuatorParameters(actuator.id, parameters)}
|
||||
|
||||
Reference in New Issue
Block a user