fix: display module dialog overlays across the full viewport

This commit is contained in:
2026-09-08 14:11:59 +09:00
parent bfb3cbaf9b
commit 5aef38a99d
7 changed files with 507 additions and 39 deletions
+39 -8
View File
@@ -17,7 +17,7 @@ import CurriculumHomeScreen from './features/curriculum/CurriculumHomeScreen'
import InstructorStudioNoticeModal from './features/curriculum/InstructorStudioNoticeModal'
import MaterialsModal from './features/curriculum/MaterialsModal'
import { enrollInCourse, fetchCurriculum, fetchStudentCourses, getDownloadResourceSizeLabel, getDownloadResourceWeek, resolveDownloadUrl, searchCourseCatalog } from './features/curriculum/curriculumApi'
import type { CustomModuleInput } from './features/workspace/ModuleListView'
import type { CustomModuleImportInput, CustomModuleInput } from './features/workspace/ModuleListView'
import type { CurriculumWeek, DownloadResource, LearningStat, StudentCourseOption } from './features/curriculum/types'
import { getContextHelpSteps } from './features/help/contextHelp'
import AiGuideView from './features/AiGuideView'
@@ -3292,7 +3292,7 @@ function LocalPcWorkspaceMockup({
openEditorFile(editorPath)
setActiveIdeActivity('workspace')
}
const createCustomModule = async (input: CustomModuleInput): Promise<void> => {
const registerCustomModule = async (input: CustomModuleInput): Promise<NonNullable<CurriculumWeek['modules']>[number]> => {
const response = await fetchWithFreshAuth(`${LMS_API_BASE_URL}/curriculum/custom-modules`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
@@ -3333,17 +3333,47 @@ function LocalPcWorkspaceMockup({
launchFile: payload.launchFile ?? input.launchFile,
runtimeTarget: payload.runtimeTarget ?? customModuleRuntimeTarget,
} satisfies NonNullable<CurriculumWeek['modules']>[number]
if (remoteWorkspace) {
await window.remoteWorkspace.modules.createCustom(getRemoteWorkspaceTarget(), customModule)
} else {
await window.localWorkspace.modules.createCustom(clonePathPreview, customModule)
}
return customModule
}
const addCustomModuleToList = (customModule: NonNullable<CurriculumWeek['modules']>[number]): void => {
setCustomModules((current) => [
...current,
{ ...customModule, sortOrder: payload.sortOrder ?? 10_000 + current.length },
{ ...customModule, sortOrder: customModule.sortOrder ?? 10_000 + current.length },
])
}
const createCustomModule = async (input: CustomModuleInput): Promise<void> => {
const customModule = await registerCustomModule(input)
const workspaceModule = {
name: customModule.name,
category: customModule.category,
folderName: customModule.folderName,
dockerImage: customModule.dockerImage ?? null,
packageName: customModule.packageName ?? input.packageName,
launchFile: customModule.launchFile ?? input.launchFile,
}
if (remoteWorkspace) {
await window.remoteWorkspace.modules.createCustom(getRemoteWorkspaceTarget(), workspaceModule)
} else {
await window.localWorkspace.modules.createCustom(clonePathPreview, workspaceModule)
}
addCustomModuleToList(customModule)
onTitlebarNotice('커스텀 모듈이 저장되었습니다.', 'info')
}
const importCustomModule = async ({ sourcePath, ...input }: CustomModuleImportInput): Promise<void> => {
if (remoteWorkspace) throw new Error('모듈 불러오기는 로컬 워크스페이스에서만 사용할 수 있습니다.')
const customModule = await registerCustomModule(input)
await window.localWorkspace.modules.importCustom(clonePathPreview, {
name: customModule.name,
category: customModule.category,
folderName: customModule.folderName,
dockerImage: customModule.dockerImage ?? null,
packageName: customModule.packageName ?? input.packageName,
launchFile: customModule.launchFile ?? input.launchFile,
sourcePath,
})
addCustomModuleToList(customModule)
onTitlebarNotice('모듈을 현재 워크스페이스로 불러왔습니다.', 'info')
}
const controlEndTime = controlEndsAt ? new Date(controlEndsAt).getTime() : Number.NaN
const controlRemainingMs = Number.isNaN(controlEndTime) ? null : Math.max(0, controlEndTime - controlCountdownNow)
const controlRemainingSeconds = controlRemainingMs === null ? null : Math.ceil(controlRemainingMs / 1000)
@@ -3911,6 +3941,7 @@ function LocalPcWorkspaceMockup({
setWorldMapSelectorOpen(true)
}}
onCreateCustomModule={createCustomModule}
onImportCustomModule={importCustomModule}
/>
</div>
)}
+7
View File
@@ -127,6 +127,13 @@ interface Window {
}
}>
createCustom: (workspaceRoot: string | undefined, input: { name: string; category: string; folderName: string; dockerImage?: string | null; packageName: string; launchFile: string }) => Promise<{ ok: boolean; workspacePath: string }>
selectImportSource: () => Promise<{
sourcePath: string
packageName: string
launchFiles: string[]
buildType: 'ament_python' | 'ament_cmake'
} | null>
importCustom: (workspaceRoot: string | undefined, input: { name: string; category: string; folderName: string; dockerImage?: string | null; packageName: string; launchFile: string; sourcePath: string }) => Promise<{ ok: boolean; workspacePath: string }>
delete: (moduleName: string, workspaceRoot?: string, folderName?: string) => Promise<{ ok: boolean }>
start: (moduleName: string, workspaceRoot?: string, folderName?: string, rosParams?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosNetwork?: RosNetworkConfig | null) => Promise<{ ok: boolean; message: string }>
build: (moduleName: string, workspaceRoot?: string, folderName?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosParams?: string) => Promise<{ ok: boolean; message: string }>
@@ -1,4 +1,4 @@
import { BookOpen, ChevronLeft, ChevronRight, PackagePlus, X } from 'lucide-react'
import { BookOpen, ChevronLeft, ChevronRight, FolderOpen, PackagePlus, X } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import type React from 'react'
import ModuleDeleteConfirm from './ModuleDeleteConfirm'
@@ -46,6 +46,7 @@ type ModuleListViewProps = {
parkingHeightStorageKey?: string
onOpenWorldEditor: () => void
onCreateCustomModule: (input: CustomModuleInput) => Promise<void>
onImportCustomModule: (input: CustomModuleImportInput) => Promise<void>
}
export default function ModuleListView({
@@ -80,12 +81,14 @@ export default function ModuleListView({
onRestoreParkedModule,
parkingHeightStorageKey,
onOpenWorldEditor,
onCreateCustomModule
onCreateCustomModule,
onImportCustomModule
}: ModuleListViewProps): React.JSX.Element {
const [moduleFilter, setModuleFilter] = useState<ModuleFilter>('all')
const [deleteConfirmModule, setDeleteConfirmModule] = useState<ModuleRow | null>(null)
const [parameterDialog, setParameterDialog] = useState<{ module: ModuleRow; parameters: ModuleParameterPair[] } | null>(null)
const [moduleAddDialogOpen, setModuleAddDialogOpen] = useState(false)
const [moduleImportDialogOpen, setModuleImportDialogOpen] = useState(false)
const [moduleGuideOpen, setModuleGuideOpen] = useState(false)
const [floatingActionMenuOpen, setFloatingActionMenuOpen] = useState(false)
const floatingActionMenuRef = useRef<HTMLDivElement>(null)
@@ -101,6 +104,7 @@ export default function ModuleListView({
const stoppedCount = modules.filter((row) => row.state === 'stopped').length
const installingAll = installingModuleName === '__all__'
const bulkControlling = installingModuleName === '__start_all__' || installingModuleName === '__stop_all__'
const hasModuleRows = modules.length > 0 || parkedModules.length > 0
useEffect(() => {
try {
@@ -234,11 +238,15 @@ export default function ModuleListView({
setModuleAddDialogOpen(false)
return
}
if (moduleImportDialogOpen) {
event.preventDefault()
setModuleImportDialogOpen(false)
}
}
window.addEventListener('keydown', handleEscape)
return () => window.removeEventListener('keydown', handleEscape)
}, [deleteConfirmModule, moduleAddDialogOpen, parameterDialog])
}, [deleteConfirmModule, moduleAddDialogOpen, moduleImportDialogOpen, parameterDialog])
return (
<section className={['relative flex h-full min-h-0 flex-col', dark ? 'bg-[#1f1f1f]' : 'bg-[#f7f8fb]'].join(' ')}>
@@ -306,7 +314,7 @@ export default function ModuleListView({
}}
>
<div className={[dark ? 'divide-y divide-white/7' : 'divide-y divide-[#eef1f6]', 'flex min-h-full flex-col'].join(' ')}>
{loading ? (
{loading && !hasModuleRows ? (
<ModuleListEmptyState dark={dark} loading />
) : filteredModuleRows.length === 0 ? (
<ModuleListEmptyState dark={dark} />
@@ -381,12 +389,36 @@ export default function ModuleListView({
</button>
<button
type="button"
aria-label="모듈 추가"
aria-label="새 모듈"
disabled={!allowCustomModuleCreation}
onClick={() => {
setModuleAddDialogOpen(true)
setFloatingActionMenuOpen(false)
}}
className={[
'absolute bottom-[180px] left-1/2 grid h-12 w-12 -translate-x-1/2 place-items-center rounded-full transition-all duration-200 ease-out',
allowCustomModuleCreation
? 'cursor-pointer bg-[#2563eb] text-white shadow-[0_8px_20px_rgba(20,54,104,0.24)] hover:scale-105 hover:bg-[#1d4ed8]'
: 'cursor-not-allowed bg-[#3b4250] text-white/35 shadow-none',
floatingActionMenuOpen ? 'pointer-events-auto translate-y-0 scale-100 opacity-100 delay-100' : 'pointer-events-none translate-y-4 scale-50 opacity-0'
].join(' ')}
>
<PackagePlus size={18} />
<span className={[
'pointer-events-none absolute left-[calc(100%+8px)] whitespace-nowrap rounded-md px-2 py-1 text-[11px] font-bold shadow-[0_6px_18px_rgba(20,54,104,0.2)]',
allowCustomModuleCreation ? 'bg-[#2563eb] text-white' : 'bg-[#3b4250] text-white/35 shadow-none'
].join(' ')}>
새 모듈
</span>
</button>
<button
type="button"
aria-label="모듈 불러오기"
disabled={!allowCustomModuleCreation}
onClick={() => {
setModuleImportDialogOpen(true)
setFloatingActionMenuOpen(false)
}}
className={[
'absolute bottom-[124px] left-1/2 grid h-12 w-12 -translate-x-1/2 place-items-center rounded-full transition-all duration-200 ease-out',
allowCustomModuleCreation
@@ -395,12 +427,12 @@ export default function ModuleListView({
floatingActionMenuOpen ? 'pointer-events-auto translate-y-0 scale-100 opacity-100 delay-75' : 'pointer-events-none translate-y-4 scale-50 opacity-0'
].join(' ')}
>
<PackagePlus size={18} />
<FolderOpen size={18} />
<span className={[
'pointer-events-none absolute left-[calc(100%+8px)] whitespace-nowrap rounded-md px-2 py-1 text-[11px] font-bold shadow-[0_6px_18px_rgba(20,54,104,0.2)]',
allowCustomModuleCreation ? 'bg-[#2563eb] text-white' : 'bg-[#3b4250] text-white/35 shadow-none'
].join(' ')}>
모듈 추가
모듈 불러오기
</span>
</button>
</div>
@@ -439,6 +471,16 @@ export default function ModuleListView({
}}
/>
)}
{allowCustomModuleCreation && moduleImportDialogOpen && (
<ModuleImportDialog
dark={dark}
onClose={() => setModuleImportDialogOpen(false)}
onImport={async (input) => {
await onImportCustomModule(input)
setModuleImportDialogOpen(false)
}}
/>
)}
{moduleGuideOpen && (
<ModuleGuideDialog
dark={dark}
@@ -616,6 +658,10 @@ export type CustomModuleInput = {
launchFile: string
}
export type CustomModuleImportInput = CustomModuleInput & {
sourcePath: string
}
function ModuleAddDialog({
dark,
onClose,
@@ -665,7 +711,7 @@ function ModuleAddDialog({
}, [])
return (
<div className="absolute inset-0 z-50 grid place-items-center bg-black/36 p-4" role="dialog" aria-modal="true" aria-label="모듈 정보 입력">
<div className="fixed inset-0 z-[200] grid place-items-center bg-black/36 p-4" role="dialog" aria-modal="true" aria-label="모듈 정보 입력">
<section className={['w-full max-w-[440px] overflow-hidden rounded-lg border shadow-[0_24px_70px_rgba(0,0,0,0.42)]', dark ? 'border-white/10 bg-[#252525] text-white' : 'border-[#d8dee9] bg-white text-[#111520]'].join(' ')}>
<header className={['flex items-center justify-between border-b px-4 py-3', dark ? 'border-white/10' : 'border-[#d8dde8]'].join(' ')}>
<div>
@@ -749,3 +795,188 @@ function ModuleAddDialog({
</div>
)
}
function ModuleImportDialog({
dark,
onClose,
onImport
}: {
dark: boolean
onClose: () => void
onImport: (input: CustomModuleImportInput) => Promise<void>
}): React.JSX.Element {
const directDockerImageOption = '__direct_input__'
const [source, setSource] = useState<{
sourcePath: string
packageName: string
launchFiles: string[]
buildType: 'ament_python' | 'ament_cmake'
} | null>(null)
const [name, setName] = useState('')
const [category, setCategory] = useState('')
const [dockerImage, setDockerImage] = useState('')
const [dockerImageSelection, setDockerImageSelection] = useState('')
const [dockerImages, setDockerImages] = useState<string[]>([])
const [dockerImagesLoading, setDockerImagesLoading] = useState(true)
const [dockerImagesMessage, setDockerImagesMessage] = useState<string | null>(null)
const [launchFile, setLaunchFile] = useState('')
const [selectingSource, setSelectingSource] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
const inputClassName = [
'h-9 w-full rounded-md border px-2.5 text-[12px] font-medium outline-none transition',
dark ? 'border-white/12 bg-[#1f1f1f] text-white/82 placeholder:text-white/28 focus:border-[#2f8dff]/55' : 'border-[#cfd6e4] bg-white text-[#111520] placeholder:text-[#9aa3b2] focus:border-[#2f8dff]/55'
].join(' ')
useEffect(() => {
let cancelled = false
void window.localWorkspace.modules.listDockerImages()
.then(({ images, message }) => {
if (cancelled) return
setDockerImages(images)
setDockerImagesMessage(message ?? null)
})
.catch((reason) => {
if (cancelled) return
setDockerImagesMessage(reason instanceof Error ? reason.message : '로컬 Docker 이미지를 불러오지 못했습니다.')
})
.finally(() => {
if (!cancelled) setDockerImagesLoading(false)
})
return () => {
cancelled = true
}
}, [])
const selectSource = (): void => {
if (selectingSource) return
setSelectingSource(true)
setError(null)
void window.localWorkspace.modules.selectImportSource()
.then((selectedSource) => {
if (!selectedSource) return
setSource(selectedSource)
setName(selectedSource.packageName)
setLaunchFile(selectedSource.launchFiles[0] ?? '')
})
.catch((reason) => setError(reason instanceof Error ? reason.message : '모듈 구조를 확인하지 못했습니다.'))
.finally(() => setSelectingSource(false))
}
return (
<div className="fixed inset-0 z-[200] grid place-items-center bg-black/36 p-4" role="dialog" aria-modal="true" aria-label="모듈 불러오기">
<section className={['w-full max-w-[480px] overflow-hidden rounded-lg border shadow-[0_24px_70px_rgba(0,0,0,0.42)]', dark ? 'border-white/10 bg-[#252525] text-white' : 'border-[#d8dee9] bg-white text-[#111520]'].join(' ')}>
<header className={['flex items-center justify-between border-b px-4 py-3', dark ? 'border-white/10' : 'border-[#d8dde8]'].join(' ')}>
<div>
<h2 className="text-[14px] font-bold">모듈 불러오기</h2>
<p className={['mt-1 text-[11px] font-medium', dark ? 'text-white/42' : 'text-[#727b8c]'].join(' ')}>ROS2 패키지 소스를 검증한 뒤 현재 워크스페이스로 복사합니다.</p>
</div>
<button type="button" onClick={onClose} className={['grid h-7 w-7 cursor-pointer place-items-center rounded-md transition', dark ? 'text-white/46 hover:bg-white/8 hover:text-white' : 'text-[#7b8494] hover:bg-[#eef1f7] hover:text-[#111520]'].join(' ')} aria-label="닫기">
<X size={15} aria-hidden="true" />
</button>
</header>
<form className="grid gap-3 p-4" onSubmit={(event) => {
event.preventDefault()
if (!source || submitting) return
setSubmitting(true)
setError(null)
void onImport({
name: name.trim(),
category,
dockerImage: dockerImage.trim(),
packageName: source.packageName,
launchFile,
sourcePath: source.sourcePath,
})
.catch((reason) => setError(reason instanceof Error ? reason.message : '모듈을 불러오지 못했습니다.'))
.finally(() => setSubmitting(false))
}}>
<div className="grid gap-1.5">
<span className={['text-[11px] font-bold', dark ? 'text-white/58' : 'text-[#596477]'].join(' ')}>모듈 경로</span>
<div className="flex gap-2">
<div className={['w-0 min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap rounded-md border px-2.5 py-2 text-[11px] font-medium', dark ? 'border-white/12 bg-[#1f1f1f] text-white/66' : 'border-[#cfd6e4] bg-[#f8fafc] text-[#596477]'].join(' ')} title={source?.sourcePath}>
{source?.sourcePath ?? '선택한 폴더 없음'}
</div>
<button type="button" onClick={selectSource} disabled={selectingSource || submitting} className="inline-flex h-9 shrink-0 cursor-pointer items-center gap-1.5 rounded-md border border-[#2563eb]/50 px-2.5 text-[12px] font-bold text-[#2f8dff] transition hover:bg-[#2563eb]/10 disabled:cursor-not-allowed disabled:opacity-45">
<FolderOpen size={14} aria-hidden="true" />
{selectingSource ? '확인 중' : '경로 선택'}
</button>
</div>
</div>
{source && (
<div className={['grid grid-cols-[1fr_auto] gap-x-3 gap-y-1 rounded-md border px-3 py-2 text-[11px] font-medium', dark ? 'border-[#2f8dff]/25 bg-[#2563eb]/10 text-white/74' : 'border-[#b7d3ff] bg-[#f2f7ff] text-[#40516b]'].join(' ')}>
<span>패키지: <strong className={dark ? 'text-white' : 'text-[#173c70]'}>{source.packageName}</strong></span>
<span>빌드: <strong className={dark ? 'text-white' : 'text-[#173c70]'}>{source.buildType}</strong></span>
<span className="col-span-2">Launch: {source.launchFiles.join(', ')}</span>
</div>
)}
<label className={['grid gap-1 text-[11px] font-bold', dark ? 'text-white/58' : 'text-[#596477]'].join(' ')}>
모듈 이름
<input value={name} onChange={(event) => setName(event.target.value)} className={inputClassName} placeholder="예: NAV2-MyPlanner" disabled={!source} autoFocus />
</label>
<label className={['grid gap-1 text-[11px] font-bold', dark ? 'text-white/58' : 'text-[#596477]'].join(' ')}>
카테고리
<select value={category} onChange={(event) => setCategory(event.target.value)} className={inputClassName} disabled={!source}>
<option value="">선택</option>
<option value="Vehicle">Vehicle</option>
<option value="Sensing">Sensing</option>
<option value="Map">Map</option>
<option value="Perception">Perception</option>
<option value="Localization">Localization</option>
<option value="Planning">Planning</option>
<option value="Control">Control</option>
<option value="System">System</option>
<option value="Simulator">Simulator</option>
<option value="Gui">Gui</option>
</select>
</label>
<label className={['grid gap-1 text-[11px] font-bold', dark ? 'text-white/58' : 'text-[#596477]'].join(' ')}>
실행 이미지
<select
value={dockerImageSelection}
disabled={!source}
onChange={(event) => {
const selection = event.target.value
setDockerImageSelection(selection)
setDockerImage(selection === directDockerImageOption ? '' : selection)
}}
className={inputClassName}
>
<option value="" disabled>선택</option>
<option value={directDockerImageOption}>직접 입력</option>
{dockerImagesLoading && <option value="__loading__" disabled>로컬 Docker 이미지 불러오는 중</option>}
{dockerImages.map((image) => <option key={image} value={image}>{image}</option>)}
</select>
</label>
{dockerImageSelection === directDockerImageOption && (
<label className={['grid gap-1 text-[11px] font-bold', dark ? 'text-white/58' : 'text-[#596477]'].join(' ')}>
직접 입력
<input value={dockerImage} onChange={(event) => setDockerImage(event.target.value)} className={inputClassName} placeholder="예: future-nav2:humble" disabled={!source} />
</label>
)}
{dockerImagesMessage && <p className={dark ? 'text-[11px] font-medium text-white/42' : 'text-[11px] font-medium text-[#7b8494]'}>{dockerImagesMessage}</p>}
<div className="grid grid-cols-2 gap-2">
<label className={['grid gap-1 text-[11px] font-bold', dark ? 'text-white/58' : 'text-[#596477]'].join(' ')}>
패키지명
<input value={source?.packageName ?? ''} readOnly className={[inputClassName, 'cursor-default opacity-70'].join(' ')} />
</label>
<label className={['grid gap-1 text-[11px] font-bold', dark ? 'text-white/58' : 'text-[#596477]'].join(' ')}>
Launch 파일
<select value={launchFile} onChange={(event) => setLaunchFile(event.target.value)} className={inputClassName} disabled={!source}>
<option value="" disabled>선택</option>
{source?.launchFiles.map((file) => <option key={file} value={file}>{file}</option>)}
</select>
</label>
</div>
{error && <p className={dark ? 'text-[11px] font-semibold text-[#ff9c9c]' : 'text-[11px] font-semibold text-[#b42323]'}>{error}</p>}
<footer className="mt-1 flex justify-end gap-2">
<button type="button" disabled={submitting} onClick={onClose} className={['h-8 cursor-pointer rounded-md border px-3 text-[12px] font-bold transition disabled:cursor-not-allowed', dark ? 'border-white/12 text-white/62 hover:bg-white/8 hover:text-white' : 'border-[#cfd6e4] text-[#687284] hover:bg-[#eef1f7] hover:text-[#111520]'].join(' ')}>취소</button>
<button type="submit" disabled={submitting || !source || !name.trim() || !category || !launchFile} className="h-8 cursor-pointer rounded-md bg-[#2563eb] px-3 text-[12px] font-bold text-white transition hover:bg-[#1d4ed8] disabled:cursor-not-allowed disabled:opacity-45">{submitting ? '불러오는 중' : '확인'}</button>
</footer>
</form>
</section>
</div>
)
}
@@ -15,6 +15,7 @@ type ModuleActionResult = {
type SavedMapPathOverrides = {
savedMapYamlPath?: string | null
savedMapDatabasePath?: string | null
savedMapWorldLayoutPath?: string | null
}
const startupCategoryOrder = ['simulator', 'vehicle', 'sensing', 'map', 'localization', 'planning', 'control', 'system']
@@ -142,14 +143,21 @@ export function useWorkspaceModuleControl({
bulkExcludedModuleKeys.includes(module.folderName ?? module.name)
), [bulkExcludedModuleKeys])
const getModuleRosParams = useCallback((module: ModuleRow, overrides?: { simulatorWorldFile?: string | null; savedMapYamlPath?: string | null; savedMapDatabasePath?: string | null }): string => {
const getModuleRosParams = useCallback((module: ModuleRow, overrides?: {
simulatorWorldFile?: string | null
savedMapYamlPath?: string | null
savedMapDatabasePath?: string | null
savedMapWorldLayoutPath?: string | null
}): string => {
const worldFile = overrides?.simulatorWorldFile ?? simulatorWorldFile
const mapYamlPath = overrides?.savedMapYamlPath ?? savedMapYamlPath
const mapDatabasePath = overrides?.savedMapDatabasePath ?? savedMapDatabasePath
const mapWorldLayoutPath = overrides?.savedMapWorldLayoutPath
const isMkMiniModule = /lms-nav2-mkmini/i.test(module.folderName ?? '')
const mkMiniProfile = vehicleRuntimeProfiles.find((profile) => profile.profileKey === 'mkmini')
const parameters = module.parameters.filter((parameter) => (
!parameter.startsWith('world:=')
&& !parameter.startsWith('world_layout_path:=')
&& !parameter.startsWith('world_layout_enabled:=')
&& !parameter.startsWith('map:=')
&& !parameter.startsWith('database_path:=')
@@ -176,6 +184,9 @@ export function useWorkspaceModuleControl({
}
if (module.category.toLowerCase() === 'simulator' && worldFile) {
parameters.push(`world:=${worldFile}`)
if (mapWorldLayoutPath) {
parameters.push(`world_layout_path:=${mapWorldLayoutPath.replace(/^\/workspace\//, '/modules/')}`)
}
}
if (module.category.toLowerCase() === 'map') {
parameters.push(`world_layout_enabled:=${worldFile === 'empty_world.world'}`)