85 lines
4.5 KiB
TypeScript
85 lines
4.5 KiB
TypeScript
|
|
/**
|
|||
|
|
* PluginManager – UI component for managing plugins
|
|||
|
|
*/
|
|||
|
|
import React, { useState, useEffect } from 'react';
|
|||
|
|
import { pluginRegistry } from '../plugins';
|
|||
|
|
import type { PluginState } from '../plugins';
|
|||
|
|
|
|||
|
|
const categoryLabels: Record<string, string> = {
|
|||
|
|
tools: 'Werkzeuge',
|
|||
|
|
elements: 'Elemente',
|
|||
|
|
'import-export': 'Import/Export',
|
|||
|
|
theme: 'Theme',
|
|||
|
|
other: 'Sonstige',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const categoryIcons: Record<string, React.ReactNode> = {
|
|||
|
|
tools: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>,
|
|||
|
|
elements: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>,
|
|||
|
|
'import-export': <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>,
|
|||
|
|
theme: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>,
|
|||
|
|
other: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const PluginManager: React.FC = () => {
|
|||
|
|
const [states, setStates] = useState<PluginState[]>([]);
|
|||
|
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const update = () => setStates(pluginRegistry.getStates());
|
|||
|
|
update();
|
|||
|
|
return pluginRegistry.subscribe(update);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const handleToggle = (id: string) => {
|
|||
|
|
pluginRegistry.toggle(id);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (states.length === 0) {
|
|||
|
|
return (
|
|||
|
|
<div style={{ padding: '16px', textAlign: 'center', color: 'var(--color-text-muted)' }}>
|
|||
|
|
Keine Plugins installiert.
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="plugin-manager">
|
|||
|
|
<div className="plugin-manager-header">
|
|||
|
|
<strong>Plugins</strong>
|
|||
|
|
<span style={{ fontSize: '11px', color: 'var(--color-text-muted)' }}>{states.filter(s => s.enabled).length} aktiv · {states.length} gesamt</span>
|
|||
|
|
</div>
|
|||
|
|
{states.map((state) => (
|
|||
|
|
<div key={state.manifest.id} className={`plugin-card ${state.enabled ? 'enabled' : ''} ${expandedId === state.manifest.id ? 'expanded' : ''}`}>
|
|||
|
|
<div className="plugin-card-header" onClick={() => setExpandedId(expandedId === state.manifest.id ? null : state.manifest.id)}>
|
|||
|
|
<div className="plugin-icon" style={{ color: state.enabled ? 'var(--color-primary)' : 'var(--color-text-muted)' }}>
|
|||
|
|
{categoryIcons[state.manifest.category] || categoryIcons.other}
|
|||
|
|
</div>
|
|||
|
|
<div className="plugin-info">
|
|||
|
|
<div className="plugin-name">{state.manifest.name}</div>
|
|||
|
|
<div className="plugin-meta">v{state.manifest.version} · {categoryLabels[state.manifest.category] || state.manifest.category}</div>
|
|||
|
|
</div>
|
|||
|
|
<button
|
|||
|
|
className={`plugin-toggle ${state.enabled ? 'on' : 'off'}`}
|
|||
|
|
onClick={(e) => { e.stopPropagation(); handleToggle(state.manifest.id); }}
|
|||
|
|
aria-label={state.enabled ? 'Deaktivieren' : 'Aktivieren'}
|
|||
|
|
role="switch"
|
|||
|
|
aria-checked={state.enabled}
|
|||
|
|
>
|
|||
|
|
<span className="plugin-toggle-knob" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
{expandedId === state.manifest.id && (
|
|||
|
|
<div className="plugin-card-body">
|
|||
|
|
<p className="plugin-description">{state.manifest.description}</p>
|
|||
|
|
<div className="plugin-author">Autor: {state.manifest.author}</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default PluginManager;
|