213 lines
7.7 KiB
TypeScript
213 lines
7.7 KiB
TypeScript
import { useState } from 'react'
|
|
import {
|
|
Puzzle,
|
|
Settings2,
|
|
Search,
|
|
Grid,
|
|
List
|
|
} from 'lucide-react'
|
|
import { useAppStore } from '../store/appStore'
|
|
import type { Plugin } from '../store/appStore'
|
|
|
|
export default function Plugins() {
|
|
const { plugins, activatePlugin, deactivatePlugin } = useAppStore()
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
|
const [_selectedPlugin, __setSelectedPlugin] = useState<Plugin | null>(null)
|
|
|
|
const filteredPlugins = plugins.filter(p =>
|
|
p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
p.description.toLowerCase().includes(searchQuery.toLowerCase())
|
|
)
|
|
|
|
const activePlugins = filteredPlugins.filter(p => p.active)
|
|
const inactivePlugins = filteredPlugins.filter(p => !p.active)
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-white">Plugins</h2>
|
|
<p className="text-text-muted mt-1">Manage your EU-Utility plugins</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3">
|
|
<div className="relative">
|
|
<Search className="w-5 h-5 text-text-muted absolute left-3 top-1/2 -translate-y-1/2" />
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="Search plugins..."
|
|
className="pl-10 pr-4 py-2 bg-surface border border-border rounded-lg text-sm focus:outline-none focus:border-primary w-64"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1 bg-surface rounded-lg p-1 border border-border">
|
|
<button
|
|
onClick={() => setViewMode('grid')}
|
|
className={`p-2 rounded ${viewMode === 'grid' ? 'bg-surface-light text-white' : 'text-text-muted'}`}
|
|
>
|
|
<Grid className="w-4 h-4" />
|
|
</button>
|
|
<button
|
|
onClick={() => setViewMode('list')}
|
|
className={`p-2 rounded ${viewMode === 'list' ? 'bg-surface-light text-white' : 'text-text-muted'}`}
|
|
>
|
|
<List className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Active Plugins */}
|
|
{activePlugins.length > 0 && (
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-text-muted uppercase tracking-wider mb-3">Active Plugins ({activePlugins.length})</h3>
|
|
<div className={viewMode === 'grid' ? 'grid grid-cols-3 gap-4' : 'space-y-2'}>
|
|
{activePlugins.map((plugin) => (
|
|
<PluginCard
|
|
key={plugin.id}
|
|
plugin={plugin}
|
|
onActivate={() => activatePlugin(plugin.id)}
|
|
onDeactivate={() => deactivatePlugin(plugin.id)}
|
|
onClick={() => _setSelectedPlugin(plugin)}
|
|
viewMode={viewMode}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Inactive Plugins */}
|
|
{inactivePlugins.length > 0 && (
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-text-muted uppercase tracking-wider mb-3">Inactive Plugins ({inactivePlugins.length})</h3>
|
|
<div className={viewMode === 'grid' ? 'grid grid-cols-3 gap-4' : 'space-y-2'}>
|
|
{inactivePlugins.map((plugin) => (
|
|
<PluginCard
|
|
key={plugin.id}
|
|
plugin={plugin}
|
|
onActivate={() => activatePlugin(plugin.id)}
|
|
onDeactivate={() => deactivatePlugin(plugin.id)}
|
|
onClick={() => _setSelectedPlugin(plugin)}
|
|
viewMode={viewMode}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty State */}
|
|
{filteredPlugins.length === 0 && (
|
|
<div className="text-center py-12">
|
|
<Puzzle className="w-16 h-16 text-text-muted mx-auto mb-4" />
|
|
<p className="text-white font-medium">No plugins found</p>
|
|
<p className="text-text-muted text-sm mt-1">Try adjusting your search</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface PluginCardProps {
|
|
plugin: Plugin
|
|
onActivate: () => void
|
|
onDeactivate: () => void
|
|
onClick: () => void
|
|
viewMode: 'grid' | 'list'
|
|
}
|
|
|
|
function PluginCard({ plugin, onActivate, onDeactivate, onClick, viewMode }: PluginCardProps) {
|
|
if (viewMode === 'list') {
|
|
return (
|
|
<div className="flex items-center gap-4 p-4 bg-surface rounded-lg border border-border hover:border-primary/30 transition-colors">
|
|
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center flex-shrink-0">
|
|
<Puzzle className="w-6 h-6 text-primary" />
|
|
</div>
|
|
|
|
<div className="flex-1 min-w-0">
|
|
<h4 className="font-semibold text-white truncate">{plugin.name}</h4>
|
|
<p className="text-sm text-text-muted truncate">{plugin.description}</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-xs text-text-muted">v{plugin.version}</span>
|
|
|
|
{plugin.active ? (
|
|
<button
|
|
onClick={onDeactivate}
|
|
className="px-3 py-1.5 bg-accent text-white text-sm font-medium rounded-lg hover:bg-accent-hover transition-colors"
|
|
>
|
|
Deactivate
|
|
</button>
|
|
) : (
|
|
<button
|
|
onClick={onActivate}
|
|
className="px-3 py-1.5 bg-primary text-white text-sm font-medium rounded-lg hover:bg-primary-hover transition-colors"
|
|
>
|
|
Activate
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="bg-surface rounded-xl p-5 border border-border hover:border-primary/30 transition-colors group">
|
|
<div className="flex items-start justify-between mb-4">
|
|
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
|
|
<Puzzle className="w-6 h-6 text-primary" />
|
|
</div>
|
|
|
|
{plugin.active ? (
|
|
<div className="flex items-center gap-1.5 px-2 py-1 bg-accent/10 rounded-full">
|
|
<div className="w-2 h-2 rounded-full bg-accent" />
|
|
<span className="text-xs font-medium text-accent">Active</span>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center gap-1.5 px-2 py-1 bg-surface-light rounded-full">
|
|
<div className="w-2 h-2 rounded-full bg-text-muted" />
|
|
<span className="text-xs font-medium text-text-muted">Inactive</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<h4 className="font-semibold text-white mb-1">{plugin.name}</h4>
|
|
<p className="text-sm text-text-muted mb-4 line-clamp-2">{plugin.description}</p>
|
|
|
|
<div className="flex items-center justify-between text-xs text-text-muted mb-4">
|
|
<span>{plugin.author}</span>
|
|
<span>v{plugin.version}</span>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
{plugin.active ? (
|
|
<>
|
|
<button
|
|
onClick={onDeactivate}
|
|
className="flex-1 px-3 py-2 bg-surface-light hover:bg-surface-light/80 text-text rounded-lg text-sm font-medium transition-colors border border-border"
|
|
>
|
|
Deactivate
|
|
</button>
|
|
<button
|
|
onClick={onClick}
|
|
className="px-3 py-2 bg-primary hover:bg-primary-hover text-white rounded-lg transition-colors"
|
|
>
|
|
<Settings2 className="w-4 h-4" />
|
|
</button>
|
|
</>
|
|
) : (
|
|
<button
|
|
onClick={onActivate}
|
|
className="w-full px-3 py-2 bg-primary hover:bg-primary-hover text-white rounded-lg text-sm font-medium transition-colors"
|
|
>
|
|
Activate
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|