feat(web-platform): release V0.7 地图模块
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, type ReactNode } from 'react';
|
||||
import { Box, FolderTree, Info, Settings2, SlidersHorizontal } from 'lucide-react';
|
||||
import type { ModelEntry } from '../../project/types';
|
||||
import { Box, FolderTree, Info, Map as MapIcon, Settings2, SlidersHorizontal } from 'lucide-react';
|
||||
import type { MapEntry, ModelEntry } from '../../project/types';
|
||||
import {
|
||||
countProjectSearchResults,
|
||||
ProjectTree,
|
||||
@@ -34,6 +34,20 @@ import { ProjectBreadcrumb } from './ProjectBreadcrumb';
|
||||
import { PythonControllerPanel } from './PythonControllerPanel';
|
||||
import { RLPolicyPanel } from './RLPolicyPanel';
|
||||
import { LocalTrainingPanel } from './LocalTrainingPanel';
|
||||
import { PhysicalMapPanel } from './PhysicalMapPanel';
|
||||
import { MapAssetLibrary } from './MapAssetLibrary';
|
||||
import {
|
||||
DEFAULT_PHYSICAL_MAP_CONFIG,
|
||||
type MapSelection,
|
||||
type SystemTerrainPreset,
|
||||
} from '../../map/types';
|
||||
import type {
|
||||
EditableMapDocument,
|
||||
EditableMapObjectType,
|
||||
MapEditorInteractionCallbacks,
|
||||
MapEditorTransformMode,
|
||||
MapObjectPlacementMode,
|
||||
} from '../../map/editor/types';
|
||||
|
||||
export function SidebarPanel({
|
||||
title,
|
||||
@@ -51,8 +65,10 @@ export function SidebarPanel({
|
||||
<aside
|
||||
className={`flex h-full w-full min-w-0 flex-col overflow-hidden bg-panel ${side === 'left' ? 'border-r' : 'border-l'} border-border`}
|
||||
>
|
||||
<h2 className="flex h-10 shrink-0 items-center gap-2 border-b border-border bg-panel px-3 text-sm font-semibold text-text-primary">
|
||||
<Settings2 aria-hidden="true" className="h-4 w-4 text-accent" />
|
||||
<h2 className="flex h-10 shrink-0 items-center gap-2 border-b border-border bg-panel/95 px-3 text-sm font-semibold tracking-tight text-text-primary">
|
||||
<span className="grid h-6 w-6 place-items-center rounded-md bg-accent-soft text-accent">
|
||||
<Settings2 aria-hidden="true" className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
{title}
|
||||
</h2>
|
||||
{children}
|
||||
@@ -69,9 +85,17 @@ export function ProjectSidebar({
|
||||
snapshot,
|
||||
loading,
|
||||
visible = true,
|
||||
nativeUrdf,
|
||||
mapSelection,
|
||||
editorDocument,
|
||||
activeTab,
|
||||
onActiveTabChange,
|
||||
onRemove,
|
||||
onSelectEntry,
|
||||
onJointHover,
|
||||
onAddMapAsset,
|
||||
onSelectTerrain,
|
||||
onSelectMapObject,
|
||||
}: {
|
||||
projectName?: string;
|
||||
files: ProjectTreeFile[];
|
||||
@@ -80,19 +104,31 @@ export function ProjectSidebar({
|
||||
snapshot?: SimulationSnapshot;
|
||||
loading: boolean;
|
||||
visible?: boolean;
|
||||
nativeUrdf: boolean;
|
||||
mapSelection: MapSelection;
|
||||
editorDocument: EditableMapDocument | null;
|
||||
activeTab?: 'project' | 'structure' | 'assets';
|
||||
onActiveTabChange?: (value: 'project' | 'structure' | 'assets') => void;
|
||||
onRemove: () => void;
|
||||
onSelectEntry: (path: string) => void;
|
||||
onJointHover: (jointId: number | null) => void;
|
||||
onAddMapAsset: (
|
||||
type: EditableMapObjectType,
|
||||
placementMode: MapObjectPlacementMode,
|
||||
) => void | Promise<void>;
|
||||
onSelectTerrain: (preset: SystemTerrainPreset) => void;
|
||||
onSelectMapObject: (id: string) => void;
|
||||
}) {
|
||||
const [tab, setTab] = useState<'project' | 'structure'>('project'),
|
||||
const [internalTab, setInternalTab] = useState<'project' | 'structure' | 'assets'>('project'),
|
||||
[fileQuery, setFileQuery] = useState(''),
|
||||
[structureQuery, setStructureQuery] = useState('');
|
||||
const fileMatches = countProjectSearchResults(files, fileQuery),
|
||||
const tab = activeTab ?? internalTab,
|
||||
fileMatches = countProjectSearchResults(files, fileQuery),
|
||||
structureMatches = snapshot
|
||||
? countModelStructureSearchResults(snapshot.bodies, snapshot.joints, structureQuery)
|
||||
: 0;
|
||||
return (
|
||||
<SidebarPanel title="工程资源" side="left" visible={visible}>
|
||||
<SidebarPanel title="资产与结构" side="left" visible={visible}>
|
||||
{projectName ? (
|
||||
<>
|
||||
<div className="flex shrink-0 items-center gap-2 border-b border-border px-3 py-2.5">
|
||||
@@ -116,7 +152,10 @@ export function ProjectSidebar({
|
||||
<Tabs
|
||||
label="工程侧栏"
|
||||
value={tab}
|
||||
onValueChange={setTab}
|
||||
onValueChange={(value) => {
|
||||
setInternalTab(value);
|
||||
onActiveTabChange?.(value);
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
value: 'project',
|
||||
@@ -170,6 +209,26 @@ export function ProjectSidebar({
|
||||
<p className="p-4 text-center text-xs text-text-tertiary">加载模型后显示结构</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'assets',
|
||||
label: '资产',
|
||||
icon: <MapIcon className="h-3.5 w-3.5" />,
|
||||
disabled: !snapshot,
|
||||
content: (
|
||||
<MapAssetLibrary
|
||||
disabled={loading || nativeUrdf}
|
||||
terrainSize={
|
||||
mapSelection.kind === 'builtin'
|
||||
? mapSelection.config.size
|
||||
: DEFAULT_PHYSICAL_MAP_CONFIG.size
|
||||
}
|
||||
document={editorDocument}
|
||||
onAdd={onAddMapAsset}
|
||||
onSelectTerrain={onSelectTerrain}
|
||||
onSelectObject={onSelectMapObject}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
@@ -199,6 +258,11 @@ interface ModelControlsProps {
|
||||
policyPaths: string[];
|
||||
selectedPolicyPath?: string;
|
||||
policyStatus?: RLPolicyStatus;
|
||||
mapSelection: MapSelection;
|
||||
maps: MapEntry[];
|
||||
showVisualMap: boolean;
|
||||
showMapCollision: boolean;
|
||||
editorDocument: EditableMapDocument | null;
|
||||
onUrdfMode: (value: UrdfLoadMode) => void;
|
||||
onBaseMode: (value: UrdfBaseMode) => void;
|
||||
onShowCollision: (value: boolean) => void;
|
||||
@@ -222,13 +286,24 @@ interface ModelControlsProps {
|
||||
onTogglePolicy: (enabled: boolean) => void;
|
||||
onPolicyCommand: (command: RLCommand) => void;
|
||||
onRemovePolicy: () => void;
|
||||
onApplyMap: (value: MapSelection) => void;
|
||||
onEditorPreview: (document: EditableMapDocument | null) => void;
|
||||
onEditorApply: (document: EditableMapDocument) => Promise<boolean>;
|
||||
onEditorExport: () => void;
|
||||
onEditorConvert: () => Promise<boolean>;
|
||||
onEditorBindInteraction: (callbacks: MapEditorInteractionCallbacks | null) => void;
|
||||
onEditorSelect: (id: string | null) => void;
|
||||
onEditorTransformMode: (mode: MapEditorTransformMode) => void;
|
||||
onEditorSnapping: (translation: number | null, rotationDegrees: number | null) => void;
|
||||
onMapDisplay: (visual: boolean, collision: boolean) => void;
|
||||
onMapTabOpen?: () => void;
|
||||
}
|
||||
export function ModelControlsSidebar(props: ModelControlsProps) {
|
||||
const [tab, setTab] = useState<'properties' | 'controls'>('properties'),
|
||||
const [tab, setTab] = useState<'properties' | 'controls' | 'map'>('properties'),
|
||||
s = props.snapshot;
|
||||
if (!s)
|
||||
return (
|
||||
<SidebarPanel title="模型与控制" side="right" visible={props.visible}>
|
||||
<SidebarPanel title="属性与参数" side="right" visible={props.visible}>
|
||||
<div className="p-4 text-sm text-text-tertiary">导入模型后显示属性</div>
|
||||
</SidebarPanel>
|
||||
);
|
||||
@@ -439,11 +514,14 @@ export function ModelControlsSidebar(props: ModelControlsProps) {
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<SidebarPanel title="模型与控制" side="right" visible={props.visible}>
|
||||
<SidebarPanel title="属性与参数" side="right" visible={props.visible}>
|
||||
<Tabs
|
||||
label="模型控制侧栏"
|
||||
value={tab}
|
||||
onValueChange={setTab}
|
||||
onValueChange={(value) => {
|
||||
setTab(value);
|
||||
if (value === 'map') props.onMapTabOpen?.();
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
value: 'properties',
|
||||
@@ -457,6 +535,41 @@ export function ModelControlsSidebar(props: ModelControlsProps) {
|
||||
icon: <SlidersHorizontal className="h-3.5 w-3.5" />,
|
||||
content: controls,
|
||||
},
|
||||
{
|
||||
value: 'map',
|
||||
label: '地图',
|
||||
icon: <MapIcon className="h-3.5 w-3.5" />,
|
||||
content: (
|
||||
<PhysicalMapPanel
|
||||
key={JSON.stringify(props.mapSelection)}
|
||||
value={props.mapSelection}
|
||||
maps={props.maps}
|
||||
rootBodies={s.bodies
|
||||
.filter(
|
||||
(body) =>
|
||||
body.id !== 0 &&
|
||||
body.parentId === 0 &&
|
||||
!body.name.startsWith('__platform_map_'),
|
||||
)
|
||||
.map((body) => body.name)}
|
||||
loading={props.loading}
|
||||
nativeUrdf={props.selectedFormat === 'urdf' && props.urdfMode === 'native'}
|
||||
showVisualMap={props.showVisualMap}
|
||||
showMapCollision={props.showMapCollision}
|
||||
editorDocument={props.editorDocument}
|
||||
onEditorPreview={props.onEditorPreview}
|
||||
onEditorApply={props.onEditorApply}
|
||||
onEditorExport={props.onEditorExport}
|
||||
onEditorConvert={props.onEditorConvert}
|
||||
onEditorBindInteraction={props.onEditorBindInteraction}
|
||||
onEditorSelect={props.onEditorSelect}
|
||||
onEditorTransformMode={props.onEditorTransformMode}
|
||||
onEditorSnapping={props.onEditorSnapping}
|
||||
onMapDisplay={props.onMapDisplay}
|
||||
onApply={props.onApplyMap}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SidebarPanel>
|
||||
|
||||
Reference in New Issue
Block a user