feat: add LMS web client
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
export default function ModuleListView({
|
||||
dark,
|
||||
modules,
|
||||
loading,
|
||||
installingModuleName,
|
||||
controllingModuleNames,
|
||||
listError,
|
||||
installError,
|
||||
onAddModuleTab,
|
||||
onOpenSource,
|
||||
onInstallModule,
|
||||
onDeleteModule,
|
||||
onUpdateModuleParameters,
|
||||
onStartModule,
|
||||
onBuildModule,
|
||||
onStopModule,
|
||||
onRestartModule,
|
||||
onInstallAll,
|
||||
onStartAll,
|
||||
onStopAll
|
||||
}: 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__'
|
||||
|
||||
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}
|
||||
/>
|
||||
<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)}
|
||||
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}
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user