Files
lms-web-fe/src/lms-renderer/features/workspace/ModuleListView.tsx
T
2026-08-03 14:06:06 +09:00

182 lines
7.2 KiB
TypeScript

import { useEffect, useState } from 'react'
import type React from 'react'
import ModuleDeleteConfirm from './ModuleDeleteConfirm'
import ModuleListEmptyState from './ModuleListEmptyState'
import ModuleListHeader from './ModuleListHeader'
import ModuleListRow from './ModuleListRow'
import ModuleListToolbar, { type ModuleFilter } from './ModuleListToolbar'
import ModuleParameterDialog from './ModuleParameterDialog'
import { createParameterId, parseModuleParameters } from './moduleParameters'
import type { ModuleParameterPair, ModuleRow } from './types'
type ModuleListViewProps = {
dark: boolean
modules: ModuleRow[]
loading: boolean
installingModuleName: string | null
controllingModuleNames: string[]
listError: string | null
installError: string | null
allowRepositoryDeletion?: boolean
onAddModuleTab: (module: ModuleRow) => void
onOpenSource: (module: ModuleRow) => void
onInstallModule: (module: ModuleRow) => void
onDeleteModule: (module: ModuleRow, options: { deleteRepository: boolean }) => void
onUpdateModuleParameters: (module: ModuleRow, parameters: ModuleParameterPair[]) => void
onStartModule: (module: ModuleRow) => void
onBuildModule: (module: ModuleRow) => void
onStopModule: (module: ModuleRow) => void
onRestartModule: (module: ModuleRow) => void
onInstallAll: () => void
onStartAll: () => void
onStopAll: () => void
onOpenWorldEditor: () => void
}
export default function ModuleListView({
dark,
modules,
loading,
installingModuleName,
controllingModuleNames,
listError,
installError,
allowRepositoryDeletion = true,
onAddModuleTab,
onOpenSource,
onInstallModule,
onDeleteModule,
onUpdateModuleParameters,
onStartModule,
onBuildModule,
onStopModule,
onRestartModule,
onInstallAll,
onStartAll,
onStopAll,
onOpenWorldEditor
}: 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 filteredModuleRows = moduleFilter === 'all' ? modules : modules.filter((row) => row.state === moduleFilter)
const uninstalledCount = modules.filter((row) => row.state === 'uninstalled').length
const runningCount = modules.filter((row) => row.state === 'running').length
const stoppedCount = modules.filter((row) => row.state === 'stopped').length
const installingAll = installingModuleName === '__all__'
const bulkControlling = installingModuleName === '__start_all__' || installingModuleName === '__stop_all__'
useEffect(() => {
const handleEscape = (event: KeyboardEvent): void => {
if (event.key !== 'Escape') return
if (parameterDialog) {
event.preventDefault()
setParameterDialog(null)
return
}
if (deleteConfirmModule) {
event.preventDefault()
setDeleteConfirmModule(null)
}
}
window.addEventListener('keydown', handleEscape)
return () => window.removeEventListener('keydown', handleEscape)
}, [deleteConfirmModule, parameterDialog])
return (
<section className={['relative flex h-full min-h-0 flex-col', dark ? 'bg-[#1f1f1f]' : 'bg-[#f7f8fb]'].join(' ')}>
<div className={['flex h-[69px] items-center justify-between p-3', dark ? 'bg-[#252525]' : 'bg-[#eef1f6]'].join(' ')}>
<div>
<strong className={['block text-[13px] font-bold', dark ? 'text-white' : 'text-[#111520]'].join(' ')}>모듈 리스트</strong>
<span className={['mt-1 block text-[11px] font-medium', dark ? 'text-white/40' : 'text-[#727b8c]'].join(' ')}>자율주행 알고리즘 모듈 관리</span>
</div>
</div>
<div className="-mt-2 flex min-h-0 flex-1 overflow-auto">
<div className={['flex min-h-full min-w-[760px] flex-1 flex-col overflow-hidden border-b', dark ? 'border-white/10 bg-[#242424]' : 'border-[#d8dee9] bg-white'].join(' ')}>
<ModuleListToolbar
dark={dark}
filter={moduleFilter}
loading={loading}
installingModuleName={installingModuleName}
uninstalledCount={uninstalledCount}
runningCount={runningCount}
stoppedCount={stoppedCount}
onFilterChange={setModuleFilter}
onInstallAll={onInstallAll}
onStartAll={onStartAll}
onStopAll={onStopAll}
onOpenWorldEditor={onOpenWorldEditor}
/>
<ModuleListHeader dark={dark} />
{(listError || installError) && (
<div className={['border-b px-3 py-2 text-[12px] font-semibold', dark ? 'border-white/10 bg-[#3a2525] text-[#ffb4b4]' : 'border-[#ffd4d4] bg-[#fff4f4] text-[#b42323]'].join(' ')}>
{listError || installError}
</div>
)}
<div className={[dark ? 'divide-y divide-white/7' : 'divide-y divide-[#eef1f6]', 'flex min-h-0 flex-1 flex-col'].join(' ')}>
{loading ? (
<ModuleListEmptyState dark={dark} loading />
) : filteredModuleRows.length === 0 ? (
<ModuleListEmptyState dark={dark} />
) : filteredModuleRows.map((row) => (
<ModuleListRow
key={row.name}
dark={dark}
module={row}
installing={installingModuleName === row.name || (installingAll && row.state === 'uninstalled')}
controlling={controllingModuleNames.includes(row.name)}
bulkControlling={bulkControlling}
onAddModuleTab={onAddModuleTab}
onOpenSource={onOpenSource}
onOpenParameters={(module) => {
const parsedParameters = parseModuleParameters(module.parameters)
setParameterDialog({
module,
parameters: parsedParameters.length
? parsedParameters
: [{ id: createParameterId(), name: '', value: '' }]
})
}}
onInstallModule={onInstallModule}
onDeleteModule={setDeleteConfirmModule}
onStartModule={onStartModule}
onBuildModule={onBuildModule}
onStopModule={onStopModule}
onRestartModule={onRestartModule}
/>
))}
</div>
</div>
</div>
{deleteConfirmModule && (
<ModuleDeleteConfirm
dark={dark}
module={deleteConfirmModule}
allowRepositoryDeletion={allowRepositoryDeletion}
onCancel={() => setDeleteConfirmModule(null)}
onConfirm={(module, options) => {
onDeleteModule(module, options)
setDeleteConfirmModule(null)
}}
/>
)}
{parameterDialog && (
<ModuleParameterDialog
dark={dark}
module={parameterDialog.module}
initialParameters={parameterDialog.parameters}
createParameterId={createParameterId}
onCancel={() => setParameterDialog(null)}
onSave={(module, parameters) => {
onUpdateModuleParameters(module, parameters)
setParameterDialog(null)
}}
/>
)}
</section>
)
}