182 lines
7.2 KiB
TypeScript
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>
|
|
)
|
|
}
|