vision modify..
This commit is contained in:
+42
-2
@@ -3,7 +3,7 @@ import type { WebContents } from 'electron'
|
|||||||
import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'
|
import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'
|
||||||
import * as pty from 'node-pty'
|
import * as pty from 'node-pty'
|
||||||
import type { IPty } from 'node-pty'
|
import type { IPty } from 'node-pty'
|
||||||
import { createWriteStream, existsSync } from 'fs'
|
import { appendFileSync, createWriteStream, existsSync, mkdirSync } from 'fs'
|
||||||
import { cp, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'fs/promises'
|
import { cp, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'fs/promises'
|
||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
import { basename, dirname, join, relative, resolve } from 'path'
|
import { basename, dirname, join, relative, resolve } from 'path'
|
||||||
@@ -33,6 +33,20 @@ const slamSimulationRuntimeImage = 'nav2-slam:humble'
|
|||||||
const slamSimulationRuntimeContainerName = 'nav2-slam'
|
const slamSimulationRuntimeContainerName = 'nav2-slam'
|
||||||
const rosBridgePort = 8765
|
const rosBridgePort = 8765
|
||||||
const exampleCodeRoot = resolve(homedir(), '다운로드', 'example-code')
|
const exampleCodeRoot = resolve(homedir(), '다운로드', 'example-code')
|
||||||
|
|
||||||
|
function writeRendererDiagnostic(kind: string, message: string): void {
|
||||||
|
try {
|
||||||
|
const logDirectory = app.getPath('logs')
|
||||||
|
mkdirSync(logDirectory, { recursive: true })
|
||||||
|
appendFileSync(
|
||||||
|
join(logDirectory, 'renderer.log'),
|
||||||
|
`[${new Date().toISOString()}] [${kind}] ${message}\n`,
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
// Diagnostics must never interrupt the LMS window itself.
|
||||||
|
}
|
||||||
|
}
|
||||||
const skippedCacheDirs = new Set(['.git', 'build', 'install', 'log', 'node_modules', 'docker'])
|
const skippedCacheDirs = new Set(['.git', 'build', 'install', 'log', 'node_modules', 'docker'])
|
||||||
const textFilePattern = /\.(c|cc|cpp|cxx|h|hpp|hh|py|xml|urdf|xacro|yaml|yml|json|txt|md|cmake|launch|sh|bash|toml|ini|cfg|rviz)$/i
|
const textFilePattern = /\.(c|cc|cpp|cxx|h|hpp|hh|py|xml|urdf|xacro|yaml|yml|json|txt|md|cmake|launch|sh|bash|toml|ini|cfg|rviz)$/i
|
||||||
const imageFilePattern = /\.(png|jpe?g|webp|gif|svg)$/i
|
const imageFilePattern = /\.(png|jpe?g|webp|gif|svg)$/i
|
||||||
@@ -4035,6 +4049,9 @@ function registerTerminalHandlers(): void {
|
|||||||
|
|
||||||
if (process.platform === 'linux' && !is.dev) {
|
if (process.platform === 'linux' && !is.dev) {
|
||||||
app.commandLine.appendSwitch('no-sandbox')
|
app.commandLine.appendSwitch('no-sandbox')
|
||||||
|
// Some packaged LMS launches run with an inaccessible /dev/shm. Keep Chromium
|
||||||
|
// shared-memory files under the app temp directory so DevTools can open.
|
||||||
|
app.commandLine.appendSwitch('disable-dev-shm-usage')
|
||||||
app.commandLine.appendSwitch('ignore-gpu-blocklist')
|
app.commandLine.appendSwitch('ignore-gpu-blocklist')
|
||||||
app.commandLine.appendSwitch('enable-unsafe-swiftshader')
|
app.commandLine.appendSwitch('enable-unsafe-swiftshader')
|
||||||
}
|
}
|
||||||
@@ -4044,7 +4061,8 @@ function attachDevToolsShortcut(win: BrowserWindow): void {
|
|||||||
if (input.type !== 'keyDown') return
|
if (input.type !== 'keyDown') return
|
||||||
const key = input.key.toLowerCase()
|
const key = input.key.toLowerCase()
|
||||||
const code = input.code.toLowerCase()
|
const code = input.code.toLowerCase()
|
||||||
if (input.key === 'F12' || code === 'f12' || ((input.control || input.meta) && input.shift && key === 'i')) {
|
// Some Linux keyboard layouts report the physical F12 key as Help.
|
||||||
|
if (input.key === 'F12' || key === 'help' || code === 'f12' || code === 'help' || ((input.control || input.meta) && input.shift && key === 'i')) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
win.webContents.toggleDevTools()
|
win.webContents.toggleDevTools()
|
||||||
}
|
}
|
||||||
@@ -4174,6 +4192,19 @@ function createWindow(): void {
|
|||||||
|
|
||||||
attachDevToolsShortcut(mainWindow)
|
attachDevToolsShortcut(mainWindow)
|
||||||
suppressSystemContextMenu(mainWindow)
|
suppressSystemContextMenu(mainWindow)
|
||||||
|
mainWindow.webContents.on('console-message', (_event, level, message, line, sourceId) => {
|
||||||
|
if (level < 2) return
|
||||||
|
writeRendererDiagnostic('console', `${sourceId}:${line} [level=${level}] ${message}`)
|
||||||
|
})
|
||||||
|
mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
|
||||||
|
writeRendererDiagnostic(
|
||||||
|
'load-failed',
|
||||||
|
`${isMainFrame ? 'main-frame' : 'sub-frame'} code=${errorCode} url=${validatedURL} message=${errorDescription}`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
mainWindow.webContents.on('render-process-gone', (_event, details) => {
|
||||||
|
writeRendererDiagnostic('renderer-gone', `reason=${details.reason} exitCode=${details.exitCode}`)
|
||||||
|
})
|
||||||
mainWindow.once('ready-to-show', () => {
|
mainWindow.once('ready-to-show', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
updateSplashStatus('로그인 세션 확인 중...')
|
updateSplashStatus('로그인 세션 확인 중...')
|
||||||
@@ -4396,6 +4427,15 @@ app.whenReady().then(() => {
|
|||||||
if (!target) return
|
if (!target) return
|
||||||
target.isMaximized() ? target.unmaximize() : target.maximize()
|
target.isMaximized() ? target.unmaximize() : target.maximize()
|
||||||
})
|
})
|
||||||
|
ipcMain.handle('titlebar-open-devtools', (event) => {
|
||||||
|
const target = BrowserWindow.fromWebContents(event.sender)
|
||||||
|
if (!target || target.isDestroyed()) return { opened: false, message: 'LMS 창을 찾지 못했습니다.' }
|
||||||
|
target.show()
|
||||||
|
target.focus()
|
||||||
|
target.webContents.openDevTools({ mode: 'detach', activate: true })
|
||||||
|
setImmediate(() => target.webContents.devToolsWebContents?.focus())
|
||||||
|
return { opened: true }
|
||||||
|
})
|
||||||
ipcMain.on('titlebar-close', (event) => BrowserWindow.fromWebContents(event.sender)?.close())
|
ipcMain.on('titlebar-close', (event) => BrowserWindow.fromWebContents(event.sender)?.close())
|
||||||
ipcMain.on('app-quit', () => app.quit())
|
ipcMain.on('app-quit', () => app.quit())
|
||||||
ipcMain.handle('learning-video:open', (event, input: LearningVideoInput) => openLearningVideoWindow(BrowserWindow.fromWebContents(event.sender), input))
|
ipcMain.handle('learning-video:open', (event, input: LearningVideoInput) => openLearningVideoWindow(BrowserWindow.fromWebContents(event.sender), input))
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ contextBridge.exposeInMainWorld('lmsBootSplash', {
|
|||||||
contextBridge.exposeInMainWorld('titlebar', {
|
contextBridge.exposeInMainWorld('titlebar', {
|
||||||
minimize: () => ipcRenderer.send('titlebar-minimize'),
|
minimize: () => ipcRenderer.send('titlebar-minimize'),
|
||||||
maximize: () => ipcRenderer.send('titlebar-maximize'),
|
maximize: () => ipcRenderer.send('titlebar-maximize'),
|
||||||
|
openDevTools: () => ipcRenderer.invoke('titlebar-open-devtools'),
|
||||||
close: () => ipcRenderer.send('titlebar-close')
|
close: () => ipcRenderer.send('titlebar-close')
|
||||||
})
|
})
|
||||||
contextBridge.exposeInMainWorld('appLifecycle', {
|
contextBridge.exposeInMainWorld('appLifecycle', {
|
||||||
|
|||||||
@@ -153,6 +153,11 @@
|
|||||||
align-items:center;
|
align-items:center;
|
||||||
gap:6px;
|
gap:6px;
|
||||||
}
|
}
|
||||||
|
#splatToggle {
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
#cameraPresetSelect {
|
#cameraPresetSelect {
|
||||||
min-width:86px;
|
min-width:86px;
|
||||||
padding:0 24px 0 9px;
|
padding:0 24px 0 9px;
|
||||||
@@ -292,8 +297,8 @@
|
|||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"three": "/vendor/three/build/three.module.js",
|
"three": "../vendor/three/build/three.module.js",
|
||||||
"three/addons/": "/vendor/three/examples/jsm/",
|
"three/addons/": "../vendor/three/examples/jsm/",
|
||||||
"@mkkellogg/gaussian-splats-3d": "./vendor/gaussian-splats-3d.module.js"
|
"@mkkellogg/gaussian-splats-3d": "./vendor/gaussian-splats-3d.module.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -977,7 +982,7 @@ async function loadMkMiniModel(sdfPose = { x:0, y:0, z:0, roll:0, pitch:0, yaw:0
|
|||||||
const initialBounds = new THREE.Box3().setFromObject(model);
|
const initialBounds = new THREE.Box3().setFromObject(model);
|
||||||
const initialSize = initialBounds.getSize(new THREE.Vector3());
|
const initialSize = initialBounds.getSize(new THREE.Vector3());
|
||||||
const largestDimension = Math.max(initialSize.x, initialSize.y, initialSize.z, 0.001);
|
const largestDimension = Math.max(initialSize.x, initialSize.y, initialSize.z, 0.001);
|
||||||
model.scale.setScalar(0.62 / largestDimension);
|
model.scale.setScalar(0.66 / largestDimension);
|
||||||
model.updateMatrixWorld(true);
|
model.updateMatrixWorld(true);
|
||||||
|
|
||||||
const normalizedBounds = new THREE.Box3().setFromObject(model);
|
const normalizedBounds = new THREE.Box3().setFromObject(model);
|
||||||
|
|||||||
@@ -3234,6 +3234,7 @@ export default function App(): React.JSX.Element {
|
|||||||
const connectedPlatformRef = useRef<PlatformOption | null>(null)
|
const connectedPlatformRef = useRef<PlatformOption | null>(null)
|
||||||
const runningPlatformsRef = useRef<PlatformOption[]>([])
|
const runningPlatformsRef = useRef<PlatformOption[]>([])
|
||||||
const platformStatusRequestSequenceRef = useRef(0)
|
const platformStatusRequestSequenceRef = useRef(0)
|
||||||
|
const [checkedRemotePlatformStatusIds, setCheckedRemotePlatformStatusIds] = useState<Set<string>>(() => new Set())
|
||||||
const forcedPlatformTerminationHandlerRef = useRef<(payload: PlatformControlTerminatedPayload) => void>(() => undefined)
|
const forcedPlatformTerminationHandlerRef = useRef<(payload: PlatformControlTerminatedPayload) => void>(() => undefined)
|
||||||
const [workspaceWeekState, setWorkspaceWeekState] = useState<CurriculumWeek | null>(null)
|
const [workspaceWeekState, setWorkspaceWeekState] = useState<CurriculumWeek | null>(null)
|
||||||
const [sessionConfirmAction, setSessionConfirmAction] = useState<'disconnect' | 'release' | null>(null)
|
const [sessionConfirmAction, setSessionConfirmAction] = useState<'disconnect' | 'release' | null>(null)
|
||||||
@@ -3641,6 +3642,11 @@ export default function App(): React.JSX.Element {
|
|||||||
return status ? { ...platform, ...status } : platform
|
return status ? { ...platform, ...status } : platform
|
||||||
}
|
}
|
||||||
setPlatformOptions((current) => current.map(mergePlatformStatus))
|
setPlatformOptions((current) => current.map(mergePlatformStatus))
|
||||||
|
setCheckedRemotePlatformStatusIds((current) => {
|
||||||
|
const next = new Set(current)
|
||||||
|
platforms.forEach((platform) => next.add(platform.id))
|
||||||
|
return next
|
||||||
|
})
|
||||||
const lostControlPlatformIds = new Set(
|
const lostControlPlatformIds = new Set(
|
||||||
runningPlatformsRef.current
|
runningPlatformsRef.current
|
||||||
.filter((platform) => {
|
.filter((platform) => {
|
||||||
@@ -3813,15 +3819,19 @@ export default function App(): React.JSX.Element {
|
|||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [authReady, authSession?.accessToken, fetchWithFreshAuth])
|
}, [authReady, authSession?.accessToken, fetchWithFreshAuth])
|
||||||
|
const isRemotePlatformStatusPending = (platform: PlatformOption): boolean => (
|
||||||
|
platform.platformKey !== 'futuredrive-local-pc' && !checkedRemotePlatformStatusIds.has(platform.id)
|
||||||
|
)
|
||||||
const filteredPlatformOptions = platformOptions.filter((platform) => {
|
const filteredPlatformOptions = platformOptions.filter((platform) => {
|
||||||
if (platformFilter === 'available') return platform.state === 'ready'
|
const pending = isRemotePlatformStatusPending(platform)
|
||||||
if (platformFilter === 'unavailable') return platform.state !== 'ready'
|
if (platformFilter === 'available') return platform.state === 'ready' && !pending
|
||||||
|
if (platformFilter === 'unavailable') return platform.state !== 'ready' && !pending
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
const platformFilterCounts = {
|
const platformFilterCounts = {
|
||||||
all: platformOptions.length,
|
all: platformOptions.length,
|
||||||
available: platformOptions.filter((platform) => platform.state === 'ready').length,
|
available: platformOptions.filter((platform) => platform.state === 'ready' && !isRemotePlatformStatusPending(platform)).length,
|
||||||
unavailable: platformOptions.filter((platform) => platform.state !== 'ready').length
|
unavailable: platformOptions.filter((platform) => platform.state !== 'ready' && !isRemotePlatformStatusPending(platform)).length
|
||||||
}
|
}
|
||||||
const localPcPlatform = platformOptions.find((platform) => platform.platformKey === 'futuredrive-local-pc')
|
const localPcPlatform = platformOptions.find((platform) => platform.platformKey === 'futuredrive-local-pc')
|
||||||
const openPlatformSettings = (platform: PlatformOption): void => {
|
const openPlatformSettings = (platform: PlatformOption): void => {
|
||||||
@@ -4306,6 +4316,8 @@ export default function App(): React.JSX.Element {
|
|||||||
setLocalPcLayoutOpen(false)
|
setLocalPcLayoutOpen(false)
|
||||||
setRecentSessionModalOpen(false)
|
setRecentSessionModalOpen(false)
|
||||||
setPlatformModalMode('select')
|
setPlatformModalMode('select')
|
||||||
|
platformStatusRequestSequenceRef.current += 1
|
||||||
|
setCheckedRemotePlatformStatusIds(new Set())
|
||||||
await loadCurriculum()
|
await loadCurriculum()
|
||||||
setPlatformModalOpen(true)
|
setPlatformModalOpen(true)
|
||||||
}
|
}
|
||||||
@@ -4828,6 +4840,7 @@ export default function App(): React.JSX.Element {
|
|||||||
counts={platformFilterCounts}
|
counts={platformFilterCounts}
|
||||||
workspaceWeekTitle={workspaceWeek.title}
|
workspaceWeekTitle={workspaceWeek.title}
|
||||||
platformLoading={platformLoading}
|
platformLoading={platformLoading}
|
||||||
|
checkedRemotePlatformStatusIds={checkedRemotePlatformStatusIds}
|
||||||
isPlatformUnavailable={isPlatformUnavailable}
|
isPlatformUnavailable={isPlatformUnavailable}
|
||||||
getPlatformDotClass={getPlatformDotClass}
|
getPlatformDotClass={getPlatformDotClass}
|
||||||
onFilterChange={setPlatformFilter}
|
onFilterChange={setPlatformFilter}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import icon from '/assets/icon.png'
|
import icon from '/assets/icon.png'
|
||||||
import { Bell, BookOpen, Building2, CheckCheck, ChevronLeft, ChevronRight, HelpCircle, LogOut, Minus, Search, Square, X } from 'lucide-react'
|
import { Bell, BookOpen, Bug, Building2, CheckCheck, ChevronLeft, ChevronRight, HelpCircle, LogOut, Minus, Search, Square, X } from 'lucide-react'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
|
|
||||||
@@ -495,6 +495,20 @@ export default function TitleBar({ user, accessToken = '', onHelpClick, onOpenCo
|
|||||||
<HelpCircle size={16} />
|
<HelpCircle size={16} />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip label="개발자 도구 열기">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Open Developer Tools"
|
||||||
|
onClick={() => {
|
||||||
|
void window.titlebar.openDevTools().then((result) => {
|
||||||
|
if (!result.opened) console.error('[titlebar] open DevTools failed:', result.message)
|
||||||
|
}).catch((error) => console.error('[titlebar] open DevTools request failed:', error))
|
||||||
|
}}
|
||||||
|
className={winButton}
|
||||||
|
>
|
||||||
|
<Bug size={15} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
<div className="relative" ref={notificationRef}>
|
<div className="relative" ref={notificationRef}>
|
||||||
<Tooltip label="알림" disabled={notificationOpen}>
|
<Tooltip label="알림" disabled={notificationOpen}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Vendored
+1
@@ -44,6 +44,7 @@ interface Window {
|
|||||||
titlebar: {
|
titlebar: {
|
||||||
minimize: () => void
|
minimize: () => void
|
||||||
maximize: () => void
|
maximize: () => void
|
||||||
|
openDevTools: () => Promise<{ opened: boolean; message?: string }>
|
||||||
close: () => void
|
close: () => void
|
||||||
}
|
}
|
||||||
appLifecycle: {
|
appLifecycle: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AlertCircle, CheckCircle2, Link2, Lock, Power, Settings } from 'lucide-react'
|
import { AlertCircle, CheckCircle2, Link2, LoaderCircle, Lock, Power, Settings } from 'lucide-react'
|
||||||
import type { LucideIcon } from 'lucide-react'
|
import type { LucideIcon } from 'lucide-react'
|
||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
import type { PlatformOption } from './types'
|
import type { PlatformOption } from './types'
|
||||||
@@ -60,6 +60,7 @@ type PlatformOptionCardProps = {
|
|||||||
Icon: LucideIcon
|
Icon: LucideIcon
|
||||||
dotClassName: string
|
dotClassName: string
|
||||||
unavailable: boolean
|
unavailable: boolean
|
||||||
|
statusPending: boolean
|
||||||
displayStatus: string
|
displayStatus: string
|
||||||
primaryAction: string
|
primaryAction: string
|
||||||
actionDisabled: boolean
|
actionDisabled: boolean
|
||||||
@@ -73,6 +74,7 @@ export function PlatformOptionCard({
|
|||||||
Icon,
|
Icon,
|
||||||
dotClassName,
|
dotClassName,
|
||||||
unavailable,
|
unavailable,
|
||||||
|
statusPending,
|
||||||
displayStatus,
|
displayStatus,
|
||||||
primaryAction,
|
primaryAction,
|
||||||
actionDisabled,
|
actionDisabled,
|
||||||
@@ -106,7 +108,9 @@ export function PlatformOptionCard({
|
|||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-[12px] font-medium text-[#5f6878]">{platform.description}</p>
|
<p className="mt-1 text-[12px] font-medium text-[#5f6878]">{platform.description}</p>
|
||||||
<div className="mt-2 flex items-center gap-1.5 text-[11px] font-bold text-[#5f6878]">
|
<div className="mt-2 flex items-center gap-1.5 text-[11px] font-bold text-[#5f6878]">
|
||||||
{unavailable
|
{statusPending
|
||||||
|
? <LoaderCircle size={13} className="animate-spin text-[#7a879a]" />
|
||||||
|
: unavailable
|
||||||
? <AlertCircle size={13} className="text-[#dc2626]" />
|
? <AlertCircle size={13} className="text-[#dc2626]" />
|
||||||
: ready
|
: ready
|
||||||
? <CheckCircle2 size={13} className="text-[#2fbda0]" />
|
? <CheckCircle2 size={13} className="text-[#2fbda0]" />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type PlatformSelectionPanelProps = {
|
|||||||
counts: Record<PlatformFilter, number>
|
counts: Record<PlatformFilter, number>
|
||||||
workspaceWeekTitle: string
|
workspaceWeekTitle: string
|
||||||
platformLoading: boolean
|
platformLoading: boolean
|
||||||
|
checkedRemotePlatformStatusIds: ReadonlySet<string>
|
||||||
isPlatformUnavailable: (platform: PlatformOption) => boolean
|
isPlatformUnavailable: (platform: PlatformOption) => boolean
|
||||||
getPlatformDotClass: (platform: PlatformOption) => string
|
getPlatformDotClass: (platform: PlatformOption) => string
|
||||||
onFilterChange: (filter: PlatformFilter) => void
|
onFilterChange: (filter: PlatformFilter) => void
|
||||||
@@ -36,6 +37,7 @@ export default function PlatformSelectionPanel({
|
|||||||
counts,
|
counts,
|
||||||
workspaceWeekTitle,
|
workspaceWeekTitle,
|
||||||
platformLoading,
|
platformLoading,
|
||||||
|
checkedRemotePlatformStatusIds,
|
||||||
isPlatformUnavailable,
|
isPlatformUnavailable,
|
||||||
getPlatformDotClass,
|
getPlatformDotClass,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
@@ -81,14 +83,17 @@ export default function PlatformSelectionPanel({
|
|||||||
const { id, status, action, state, iconKey } = platform
|
const { id, status, action, state, iconKey } = platform
|
||||||
const Icon = PLATFORM_ICONS[iconKey] ?? Monitor
|
const Icon = PLATFORM_ICONS[iconKey] ?? Monitor
|
||||||
const locked = state === 'locked'
|
const locked = state === 'locked'
|
||||||
const unavailable = isPlatformUnavailable(platform)
|
const statusPending = platform.platformKey !== 'futuredrive-local-pc' && !checkedRemotePlatformStatusIds.has(id)
|
||||||
const actionDisabled = locked || unavailable
|
const unavailable = !statusPending && isPlatformUnavailable(platform)
|
||||||
|
const actionDisabled = statusPending || locked || unavailable
|
||||||
const runningInSelectionList = runningPlatforms.some((runningPlatform) => runningPlatform.id === platform.id)
|
const runningInSelectionList = runningPlatforms.some((runningPlatform) => runningPlatform.id === platform.id)
|
||||||
const displayStatus = unavailable && !status.includes('연결 끊김') && !status.includes('연결 불가')
|
const displayStatus = statusPending
|
||||||
|
? '확인 중'
|
||||||
|
: unavailable && !status.includes('연결 끊김') && !status.includes('연결 불가')
|
||||||
? '연결 끊김'
|
? '연결 끊김'
|
||||||
: status
|
: status
|
||||||
const hasControl = platform.control?.status === 'ACTIVE'
|
const hasControl = platform.control?.status === 'ACTIVE'
|
||||||
const primaryAction = hasControl ? '연결' : action
|
const primaryAction = statusPending ? '확인 중' : hasControl ? '연결' : action
|
||||||
const platformActionDisabled = runningInSelectionList || actionDisabled
|
const platformActionDisabled = runningInSelectionList || actionDisabled
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,8 +101,9 @@ export default function PlatformSelectionPanel({
|
|||||||
key={id}
|
key={id}
|
||||||
platform={platform}
|
platform={platform}
|
||||||
Icon={Icon}
|
Icon={Icon}
|
||||||
dotClassName={getPlatformDotClass(platform)}
|
dotClassName={statusPending ? 'bg-[#94a3b8] shadow-[0_0_6px_rgba(148,163,184,0.45)]' : getPlatformDotClass(platform)}
|
||||||
unavailable={unavailable}
|
unavailable={unavailable}
|
||||||
|
statusPending={statusPending}
|
||||||
displayStatus={displayStatus}
|
displayStatus={displayStatus}
|
||||||
primaryAction={primaryAction}
|
primaryAction={primaryAction}
|
||||||
actionDisabled={platformActionDisabled}
|
actionDisabled={platformActionDisabled}
|
||||||
|
|||||||
@@ -59,11 +59,11 @@ function createMesh(item: WorldLayoutObject): THREE.Mesh {
|
|||||||
return mesh
|
return mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelRoot = '/slam-viewer/turtlebot3_world_model/turtlebot3_world'
|
const modelRoot = './slam-viewer/turtlebot3_world_model/turtlebot3_world'
|
||||||
const modelRoots: Record<string, string> = {
|
const modelRoots: Record<string, string> = {
|
||||||
turtlebot3_world: modelRoot,
|
turtlebot3_world: modelRoot,
|
||||||
turtlebot3_house: '/slam-viewer/turtlebot3_world_model/turtlebot3_house',
|
turtlebot3_house: './slam-viewer/turtlebot3_world_model/turtlebot3_house',
|
||||||
turtlebot3_common: '/slam-viewer/turtlebot3_world_model/turtlebot3_common'
|
turtlebot3_common: './slam-viewer/turtlebot3_world_model/turtlebot3_common'
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNumbers(value: string | null | undefined, count = 0): number[] {
|
function parseNumbers(value: string | null | undefined, count = 0): number[] {
|
||||||
@@ -97,7 +97,7 @@ function worldMaterial(visual: Element): THREE.MeshLambertMaterial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function gazeboAssetPath(uri: string): string {
|
function gazeboAssetPath(uri: string): string {
|
||||||
return uri.replace(/^model:\/\/(\w+)\//, (_match, name: string) => `${modelRoots[name] ?? `/slam-viewer/turtlebot3_world_model/${name}`}/`)
|
return uri.replace(/^model:\/\/(\w+)\//, (_match, name: string) => `${modelRoots[name] ?? `./slam-viewer/turtlebot3_world_model/${name}`}/`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadWorldSdf(parent: THREE.Group): Promise<void> {
|
async function loadWorldSdf(parent: THREE.Group): Promise<void> {
|
||||||
@@ -470,7 +470,7 @@ export default function WorldEditorModal({ workspaceRoot, onClose, onSaved }: Wo
|
|||||||
</aside>
|
</aside>
|
||||||
<main className="relative min-w-0 flex-1 bg-[#0b1220]">
|
<main className="relative min-w-0 flex-1 bg-[#0b1220]">
|
||||||
<div ref={canvasHostRef} className="pointer-events-none absolute inset-0 opacity-0" />
|
<div ref={canvasHostRef} className="pointer-events-none absolute inset-0 opacity-0" />
|
||||||
<iframe ref={viewerRef} title="TurtleBot3 World Editor" src="/slam-viewer/index.html?editor=1&mapKey=nav2-custom" className="absolute inset-0 z-10 size-full border-0" />
|
<iframe ref={viewerRef} title="TurtleBot3 World Editor" src="./slam-viewer/index.html?editor=1&mapKey=nav2-custom" className="absolute inset-0 z-10 size-full border-0" />
|
||||||
{loading && <div className="absolute inset-0 z-20 grid place-items-center bg-[#0b1220] text-[12px] font-semibold text-white/58"><span className="flex items-center gap-2"><LoaderCircle size={16} className="animate-spin" />월드 불러오는 중</span></div>}
|
{loading && <div className="absolute inset-0 z-20 grid place-items-center bg-[#0b1220] text-[12px] font-semibold text-white/58"><span className="flex items-center gap-2"><LoaderCircle size={16} className="animate-spin" />월드 불러오는 중</span></div>}
|
||||||
<div className="absolute left-3 top-3 z-20 flex overflow-hidden rounded-md border border-white/15 bg-[#15223a]/95 shadow">
|
<div className="absolute left-3 top-3 z-20 flex overflow-hidden rounded-md border border-white/15 bg-[#15223a]/95 shadow">
|
||||||
{([
|
{([
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ export function useWorkspaceVisualization({
|
|||||||
const visualizationRuntimeStack = visualizationStack === 'slam' || visualizationStack === 'slam3d' ? 'nav2' : visualizationStack
|
const visualizationRuntimeStack = visualizationStack === 'slam' || visualizationStack === 'slam3d' ? 'nav2' : visualizationStack
|
||||||
const pointCloudQuery = visualizationStack === 'slam3d' ? '&pointCloud=1' : ''
|
const pointCloudQuery = visualizationStack === 'slam3d' ? '&pointCloud=1' : ''
|
||||||
const visualizationViewerUrl = visualizationStack === 'slam' || visualizationStack === 'slam3d' || visualizationStack === 'nav2'
|
const visualizationViewerUrl = visualizationStack === 'slam' || visualizationStack === 'slam3d' || visualizationStack === 'nav2'
|
||||||
? `/slam-viewer/index.html?directBridge=1&mode=${visualizationStack === 'nav2' ? nav2VisualizationMode : 'slam'}&runtime=${isRealPlatformProfile(activeEnvironmentProfile) ? 'real' : 'simulation'}&bridgeWs=${encodeURIComponent(`${visualizationBridgeUrl}/ws/state`)}&bridgeHttp=${encodeURIComponent(visualizationBridgeHttpUrl)}&mapKey=${encodeURIComponent(mapKey ?? 'nav2-turtlebot3-world')}&layoutVersion=${worldLayoutVersion ?? 0}${pointCloudQuery}`
|
? `./slam-viewer/index.html?directBridge=1&mode=${visualizationStack === 'nav2' ? nav2VisualizationMode : 'slam'}&runtime=${isRealPlatformProfile(activeEnvironmentProfile) ? 'real' : 'simulation'}&bridgeWs=${encodeURIComponent(`${visualizationBridgeUrl}/ws/state`)}&bridgeHttp=${encodeURIComponent(visualizationBridgeHttpUrl)}&mapKey=${encodeURIComponent(mapKey ?? 'nav2-turtlebot3-world')}&layoutVersion=${worldLayoutVersion ?? 0}${pointCloudQuery}`
|
||||||
: `/autoware-viewer/index.html?source=ros&stack=${visualizationStack}&bridge=${encodeURIComponent(visualizationBridgeUrl)}&backend=${encodeURIComponent(visualizationBackendUrl)}`
|
: `./autoware-viewer/index.html?source=ros&stack=${visualizationStack}&bridge=${encodeURIComponent(visualizationBridgeUrl)}&backend=${encodeURIComponent(visualizationBackendUrl)}`
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fallback = runtimeProfiles[0] ? resolveVisualizationStack(runtimeProfiles[0].profileType) : null
|
const fallback = runtimeProfiles[0] ? resolveVisualizationStack(runtimeProfiles[0].profileType) : null
|
||||||
|
|||||||
Reference in New Issue
Block a user