124 lines
3.9 KiB
TypeScript
124 lines
3.9 KiB
TypeScript
import { Download, MapPinned, Play, RefreshCw, Square } from 'lucide-react'
|
|
import type React from 'react'
|
|
|
|
export type ModuleFilter = 'all' | 'running' | 'stopped' | 'uninstalled'
|
|
|
|
type ModuleListToolbarProps = {
|
|
dark: boolean
|
|
filter: ModuleFilter
|
|
loading: boolean
|
|
installingModuleName: string | null
|
|
uninstalledCount: number
|
|
runningCount: number
|
|
stoppedCount: number
|
|
onFilterChange: (filter: ModuleFilter) => void
|
|
onInstallAll: () => void
|
|
onStartAll: () => void
|
|
onStopAll: () => void
|
|
onOpenWorldEditor: () => void
|
|
}
|
|
|
|
const FILTER_TABS: Array<{ id: ModuleFilter; label: string }> = [
|
|
{ id: 'all', label: '전체' },
|
|
{ id: 'running', label: '실행중' },
|
|
{ id: 'stopped', label: '정지됨' },
|
|
{ id: 'uninstalled', label: '미설치' }
|
|
]
|
|
|
|
export default function ModuleListToolbar({
|
|
dark,
|
|
filter,
|
|
loading,
|
|
installingModuleName,
|
|
uninstalledCount,
|
|
runningCount,
|
|
stoppedCount,
|
|
onFilterChange,
|
|
onInstallAll,
|
|
onStartAll,
|
|
onStopAll,
|
|
onOpenWorldEditor
|
|
}: ModuleListToolbarProps): React.JSX.Element {
|
|
const installingAll = installingModuleName === '__all__'
|
|
const startingAll = installingModuleName === '__start_all__'
|
|
const stoppingAll = installingModuleName === '__stop_all__'
|
|
|
|
const bulkActions = [
|
|
{ label: '맵설정', Icon: MapPinned, busy: false, disabled: false, onClick: onOpenWorldEditor },
|
|
{
|
|
label: '전체 설치',
|
|
Icon: Download,
|
|
busy: installingAll,
|
|
disabled: loading || installingModuleName !== null || uninstalledCount === 0,
|
|
onClick: onInstallAll
|
|
},
|
|
{
|
|
label: '전체 실행',
|
|
Icon: Play,
|
|
busy: startingAll,
|
|
disabled: loading || installingModuleName !== null || stoppedCount === 0,
|
|
onClick: onStartAll
|
|
},
|
|
{
|
|
label: '전체 중지',
|
|
Icon: Square,
|
|
busy: stoppingAll,
|
|
disabled: loading || installingModuleName !== null || runningCount === 0,
|
|
onClick: onStopAll
|
|
}
|
|
]
|
|
|
|
return (
|
|
<div className={['flex h-10 items-end justify-between px-3', dark ? 'bg-[#252525]' : 'bg-[#f7f8fb]'].join(' ')}>
|
|
<div className="flex items-end">
|
|
{FILTER_TABS.map(({ id, label }) => {
|
|
const active = filter === id
|
|
|
|
return (
|
|
<button
|
|
key={id}
|
|
type="button"
|
|
onClick={() => onFilterChange(id)}
|
|
className={[
|
|
'h-8 min-w-[76px] cursor-pointer border-x border-t px-3 text-[12px] font-bold transition first:rounded-tl-md last:rounded-tr-md',
|
|
active
|
|
? dark
|
|
? 'border-white/10 bg-[#2b2b2b] text-white'
|
|
: 'border-[#e1e6ef] bg-white text-[#111520]'
|
|
: dark
|
|
? 'border-white/8 bg-[#202020] text-white/45 hover:text-white/70'
|
|
: 'border-[#e1e6ef] bg-[#eef1f6] text-[#687284] hover:text-[#111520]'
|
|
].join(' ')}
|
|
>
|
|
{label}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
<div className="flex items-center gap-2 pb-2">
|
|
{bulkActions.map(({ label, Icon, busy, disabled, onClick }) => (
|
|
<button
|
|
key={label}
|
|
type="button"
|
|
disabled={disabled}
|
|
onClick={() => {
|
|
if (!onClick) return
|
|
onClick()
|
|
}}
|
|
className={[
|
|
'flex h-7 items-center gap-1.5 rounded-md border px-3 text-[12px] font-bold transition',
|
|
disabled ? 'cursor-default opacity-45' : 'cursor-pointer',
|
|
dark
|
|
? 'border-white/15 bg-white/[0.04] text-white/70 hover:border-white/25 hover:bg-white/10 hover:text-white'
|
|
: 'border-[#cfd6e4] bg-white text-[#4f596b] hover:bg-[#eef1f7] hover:text-[#111520]'
|
|
].join(' ')}
|
|
>
|
|
{busy ? <RefreshCw size={14} className="animate-spin" /> : <Icon size={14} />}
|
|
{label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|