插件管理器
This commit is contained in:
@@ -7,6 +7,7 @@ import { SceneHierarchy } from './components/SceneHierarchy';
|
||||
import { EntityInspector } from './components/EntityInspector';
|
||||
import { AssetBrowser } from './components/AssetBrowser';
|
||||
import { ConsolePanel } from './components/ConsolePanel';
|
||||
import { PluginManagerWindow } from './components/PluginManagerWindow';
|
||||
import { Viewport } from './components/Viewport';
|
||||
import { MenuBar } from './components/MenuBar';
|
||||
import { DockContainer, DockablePanel } from './components/DockContainer';
|
||||
@@ -36,6 +37,7 @@ function App() {
|
||||
const { t, locale, changeLocale } = useLocale();
|
||||
const [status, setStatus] = useState(t('header.status.initializing'));
|
||||
const [panels, setPanels] = useState<DockablePanel[]>([]);
|
||||
const [showPluginManager, setShowPluginManager] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const initializeEditor = async () => {
|
||||
@@ -280,6 +282,7 @@ function App() {
|
||||
onOpenProject={handleOpenProject}
|
||||
onCloseProject={handleCloseProject}
|
||||
onExit={handleExit}
|
||||
onOpenPluginManager={() => setShowPluginManager(true)}
|
||||
/>
|
||||
<div className="header-right">
|
||||
<button onClick={handleLocaleChange} className="toolbar-btn locale-btn" title={locale === 'en' ? '切换到中文' : 'Switch to English'}>
|
||||
@@ -298,6 +301,13 @@ function App() {
|
||||
<span>{t('footer.entities')}: {entityStore?.getAllEntities().length ?? 0}</span>
|
||||
<span>{t('footer.core')}: {initialized ? t('footer.active') : t('footer.inactive')}</span>
|
||||
</div>
|
||||
|
||||
{showPluginManager && pluginManager && (
|
||||
<PluginManagerWindow
|
||||
pluginManager={pluginManager}
|
||||
onClose={() => setShowPluginManager(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ interface MenuBarProps {
|
||||
onOpenProject?: () => void;
|
||||
onCloseProject?: () => void;
|
||||
onExit?: () => void;
|
||||
onOpenPluginManager?: () => void;
|
||||
}
|
||||
|
||||
export function MenuBar({
|
||||
@@ -29,7 +30,8 @@ export function MenuBar({
|
||||
onSaveSceneAs,
|
||||
onOpenProject,
|
||||
onCloseProject,
|
||||
onExit
|
||||
onExit,
|
||||
onOpenPluginManager
|
||||
}: MenuBarProps) {
|
||||
const [openMenu, setOpenMenu] = useState<string | null>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
@@ -59,6 +61,7 @@ export function MenuBar({
|
||||
assets: 'Assets',
|
||||
console: 'Console',
|
||||
viewport: 'Viewport',
|
||||
pluginManager: 'Plugin Manager',
|
||||
help: 'Help',
|
||||
documentation: 'Documentation',
|
||||
about: 'About'
|
||||
@@ -86,6 +89,7 @@ export function MenuBar({
|
||||
assets: '资产',
|
||||
console: '控制台',
|
||||
viewport: '视口',
|
||||
pluginManager: '插件管理器',
|
||||
help: '帮助',
|
||||
documentation: '文档',
|
||||
about: '关于'
|
||||
@@ -123,7 +127,9 @@ export function MenuBar({
|
||||
{ label: t('inspector'), disabled: true },
|
||||
{ label: t('assets'), disabled: true },
|
||||
{ label: t('console'), disabled: true },
|
||||
{ label: t('viewport'), disabled: true }
|
||||
{ label: t('viewport'), disabled: true },
|
||||
{ separator: true },
|
||||
{ label: t('pluginManager'), onClick: onOpenPluginManager }
|
||||
],
|
||||
help: [
|
||||
{ label: t('documentation'), disabled: true },
|
||||
|
||||
253
packages/editor-app/src/components/PluginManagerWindow.tsx
Normal file
253
packages/editor-app/src/components/PluginManagerWindow.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { EditorPluginManager, IEditorPluginMetadata, EditorPluginCategory } from '@esengine/editor-core';
|
||||
import { Package, CheckCircle, XCircle, Search, Grid, List, ChevronDown, ChevronRight, X } from 'lucide-react';
|
||||
import '../styles/PluginManagerWindow.css';
|
||||
|
||||
interface PluginManagerWindowProps {
|
||||
pluginManager: EditorPluginManager;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const categoryIcons: Record<EditorPluginCategory, string> = {
|
||||
[EditorPluginCategory.Tool]: '🔧',
|
||||
[EditorPluginCategory.Window]: '🪟',
|
||||
[EditorPluginCategory.Inspector]: '🔍',
|
||||
[EditorPluginCategory.System]: '⚙️',
|
||||
[EditorPluginCategory.ImportExport]: '📦'
|
||||
};
|
||||
|
||||
const categoryNames: Record<EditorPluginCategory, string> = {
|
||||
[EditorPluginCategory.Tool]: 'Tools',
|
||||
[EditorPluginCategory.Window]: 'Windows',
|
||||
[EditorPluginCategory.Inspector]: 'Inspectors',
|
||||
[EditorPluginCategory.System]: 'System',
|
||||
[EditorPluginCategory.ImportExport]: 'Import/Export'
|
||||
};
|
||||
|
||||
export function PluginManagerWindow({ pluginManager, onClose }: PluginManagerWindowProps) {
|
||||
const [plugins, setPlugins] = useState<IEditorPluginMetadata[]>([]);
|
||||
const [filter, setFilter] = useState('');
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('list');
|
||||
const [expandedCategories, setExpandedCategories] = useState<Set<EditorPluginCategory>>(
|
||||
new Set(Object.values(EditorPluginCategory))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const updatePlugins = () => {
|
||||
const allPlugins = pluginManager.getAllPluginMetadata();
|
||||
setPlugins(allPlugins);
|
||||
};
|
||||
|
||||
updatePlugins();
|
||||
}, [pluginManager]);
|
||||
|
||||
const togglePlugin = async (name: string, enabled: boolean) => {
|
||||
try {
|
||||
if (enabled) {
|
||||
await pluginManager.disablePlugin(name);
|
||||
} else {
|
||||
await pluginManager.enablePlugin(name);
|
||||
}
|
||||
const allPlugins = pluginManager.getAllPluginMetadata();
|
||||
setPlugins(allPlugins);
|
||||
} catch (error) {
|
||||
console.error(`Failed to toggle plugin ${name}:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleCategory = (category: EditorPluginCategory) => {
|
||||
const newExpanded = new Set(expandedCategories);
|
||||
if (newExpanded.has(category)) {
|
||||
newExpanded.delete(category);
|
||||
} else {
|
||||
newExpanded.add(category);
|
||||
}
|
||||
setExpandedCategories(newExpanded);
|
||||
};
|
||||
|
||||
const filteredPlugins = plugins.filter(plugin => {
|
||||
if (!filter) return true;
|
||||
const searchLower = filter.toLowerCase();
|
||||
return (
|
||||
plugin.name.toLowerCase().includes(searchLower) ||
|
||||
plugin.displayName.toLowerCase().includes(searchLower) ||
|
||||
plugin.description?.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
|
||||
const pluginsByCategory = filteredPlugins.reduce((acc, plugin) => {
|
||||
if (!acc[plugin.category]) {
|
||||
acc[plugin.category] = [];
|
||||
}
|
||||
acc[plugin.category].push(plugin);
|
||||
return acc;
|
||||
}, {} as Record<EditorPluginCategory, IEditorPluginMetadata[]>);
|
||||
|
||||
const enabledCount = plugins.filter(p => p.enabled).length;
|
||||
const disabledCount = plugins.filter(p => !p.enabled).length;
|
||||
|
||||
const renderPluginCard = (plugin: IEditorPluginMetadata) => (
|
||||
<div key={plugin.name} className={`plugin-card ${plugin.enabled ? 'enabled' : 'disabled'}`}>
|
||||
<div className="plugin-card-header">
|
||||
<div className="plugin-card-icon">
|
||||
{plugin.icon || <Package size={24} />}
|
||||
</div>
|
||||
<div className="plugin-card-info">
|
||||
<div className="plugin-card-title">{plugin.displayName}</div>
|
||||
<div className="plugin-card-version">v{plugin.version}</div>
|
||||
</div>
|
||||
<button
|
||||
className={`plugin-toggle ${plugin.enabled ? 'enabled' : 'disabled'}`}
|
||||
onClick={() => togglePlugin(plugin.name, plugin.enabled)}
|
||||
title={plugin.enabled ? 'Disable plugin' : 'Enable plugin'}
|
||||
>
|
||||
{plugin.enabled ? <CheckCircle size={18} /> : <XCircle size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<div className="plugin-card-description">{plugin.description}</div>
|
||||
)}
|
||||
<div className="plugin-card-footer">
|
||||
<span className="plugin-card-category">
|
||||
{categoryIcons[plugin.category]} {categoryNames[plugin.category]}
|
||||
</span>
|
||||
{plugin.installedAt && (
|
||||
<span className="plugin-card-installed">
|
||||
Installed: {new Date(plugin.installedAt).toLocaleDateString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderPluginList = (plugin: IEditorPluginMetadata) => (
|
||||
<div key={plugin.name} className={`plugin-list-item ${plugin.enabled ? 'enabled' : 'disabled'}`}>
|
||||
<div className="plugin-list-icon">
|
||||
{plugin.icon || <Package size={20} />}
|
||||
</div>
|
||||
<div className="plugin-list-info">
|
||||
<div className="plugin-list-name">
|
||||
{plugin.displayName}
|
||||
<span className="plugin-list-version">v{plugin.version}</span>
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<div className="plugin-list-description">{plugin.description}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="plugin-list-status">
|
||||
{plugin.enabled ? (
|
||||
<span className="status-badge enabled">Enabled</span>
|
||||
) : (
|
||||
<span className="status-badge disabled">Disabled</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="plugin-list-toggle"
|
||||
onClick={() => togglePlugin(plugin.name, plugin.enabled)}
|
||||
title={plugin.enabled ? 'Disable plugin' : 'Enable plugin'}
|
||||
>
|
||||
{plugin.enabled ? 'Disable' : 'Enable'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="plugin-manager-overlay" onClick={onClose}>
|
||||
<div className="plugin-manager-window" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="plugin-manager-header">
|
||||
<div className="plugin-manager-title">
|
||||
<Package size={20} />
|
||||
<h2>Plugin Manager</h2>
|
||||
</div>
|
||||
<button className="plugin-manager-close" onClick={onClose} title="Close">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="plugin-toolbar">
|
||||
<div className="plugin-toolbar-left">
|
||||
<div className="plugin-search">
|
||||
<Search size={14} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search plugins..."
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="plugin-toolbar-right">
|
||||
<div className="plugin-stats">
|
||||
<span className="stat-item enabled">
|
||||
<CheckCircle size={14} />
|
||||
{enabledCount} enabled
|
||||
</span>
|
||||
<span className="stat-item disabled">
|
||||
<XCircle size={14} />
|
||||
{disabledCount} disabled
|
||||
</span>
|
||||
</div>
|
||||
<div className="plugin-view-mode">
|
||||
<button
|
||||
className={viewMode === 'list' ? 'active' : ''}
|
||||
onClick={() => setViewMode('list')}
|
||||
title="List view"
|
||||
>
|
||||
<List size={14} />
|
||||
</button>
|
||||
<button
|
||||
className={viewMode === 'grid' ? 'active' : ''}
|
||||
onClick={() => setViewMode('grid')}
|
||||
title="Grid view"
|
||||
>
|
||||
<Grid size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="plugin-content">
|
||||
{plugins.length === 0 ? (
|
||||
<div className="plugin-empty">
|
||||
<Package size={48} />
|
||||
<p>No plugins installed</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="plugin-categories">
|
||||
{Object.entries(pluginsByCategory).map(([category, categoryPlugins]) => {
|
||||
const cat = category as EditorPluginCategory;
|
||||
const isExpanded = expandedCategories.has(cat);
|
||||
|
||||
return (
|
||||
<div key={category} className="plugin-category">
|
||||
<div
|
||||
className="plugin-category-header"
|
||||
onClick={() => toggleCategory(cat)}
|
||||
>
|
||||
<button className="plugin-category-toggle">
|
||||
{isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
</button>
|
||||
<span className="plugin-category-icon">{categoryIcons[cat]}</span>
|
||||
<span className="plugin-category-name">{categoryNames[cat]}</span>
|
||||
<span className="plugin-category-count">
|
||||
{categoryPlugins.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className={`plugin-category-content ${viewMode}`}>
|
||||
{viewMode === 'grid'
|
||||
? categoryPlugins.map(renderPluginCard)
|
||||
: categoryPlugins.map(renderPluginList)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
240
packages/editor-app/src/components/PluginPanel.tsx
Normal file
240
packages/editor-app/src/components/PluginPanel.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { EditorPluginManager, IEditorPluginMetadata, EditorPluginCategory } from '@esengine/editor-core';
|
||||
import { Package, CheckCircle, XCircle, Search, Grid, List, ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import '../styles/PluginPanel.css';
|
||||
|
||||
interface PluginPanelProps {
|
||||
pluginManager: EditorPluginManager;
|
||||
}
|
||||
|
||||
const categoryIcons: Record<EditorPluginCategory, string> = {
|
||||
[EditorPluginCategory.Tool]: '🔧',
|
||||
[EditorPluginCategory.Window]: '🪟',
|
||||
[EditorPluginCategory.Inspector]: '🔍',
|
||||
[EditorPluginCategory.System]: '⚙️',
|
||||
[EditorPluginCategory.ImportExport]: '📦'
|
||||
};
|
||||
|
||||
const categoryNames: Record<EditorPluginCategory, string> = {
|
||||
[EditorPluginCategory.Tool]: 'Tools',
|
||||
[EditorPluginCategory.Window]: 'Windows',
|
||||
[EditorPluginCategory.Inspector]: 'Inspectors',
|
||||
[EditorPluginCategory.System]: 'System',
|
||||
[EditorPluginCategory.ImportExport]: 'Import/Export'
|
||||
};
|
||||
|
||||
export function PluginPanel({ pluginManager }: PluginPanelProps) {
|
||||
const [plugins, setPlugins] = useState<IEditorPluginMetadata[]>([]);
|
||||
const [filter, setFilter] = useState('');
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('list');
|
||||
const [expandedCategories, setExpandedCategories] = useState<Set<EditorPluginCategory>>(
|
||||
new Set(Object.values(EditorPluginCategory))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const updatePlugins = () => {
|
||||
const allPlugins = pluginManager.getAllPluginMetadata();
|
||||
setPlugins(allPlugins);
|
||||
};
|
||||
|
||||
updatePlugins();
|
||||
}, [pluginManager]);
|
||||
|
||||
const togglePlugin = async (name: string, enabled: boolean) => {
|
||||
try {
|
||||
if (enabled) {
|
||||
await pluginManager.disablePlugin(name);
|
||||
} else {
|
||||
await pluginManager.enablePlugin(name);
|
||||
}
|
||||
const allPlugins = pluginManager.getAllPluginMetadata();
|
||||
setPlugins(allPlugins);
|
||||
} catch (error) {
|
||||
console.error(`Failed to toggle plugin ${name}:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleCategory = (category: EditorPluginCategory) => {
|
||||
const newExpanded = new Set(expandedCategories);
|
||||
if (newExpanded.has(category)) {
|
||||
newExpanded.delete(category);
|
||||
} else {
|
||||
newExpanded.add(category);
|
||||
}
|
||||
setExpandedCategories(newExpanded);
|
||||
};
|
||||
|
||||
const filteredPlugins = plugins.filter(plugin => {
|
||||
if (!filter) return true;
|
||||
const searchLower = filter.toLowerCase();
|
||||
return (
|
||||
plugin.name.toLowerCase().includes(searchLower) ||
|
||||
plugin.displayName.toLowerCase().includes(searchLower) ||
|
||||
plugin.description?.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
|
||||
const pluginsByCategory = filteredPlugins.reduce((acc, plugin) => {
|
||||
if (!acc[plugin.category]) {
|
||||
acc[plugin.category] = [];
|
||||
}
|
||||
acc[plugin.category].push(plugin);
|
||||
return acc;
|
||||
}, {} as Record<EditorPluginCategory, IEditorPluginMetadata[]>);
|
||||
|
||||
const enabledCount = plugins.filter(p => p.enabled).length;
|
||||
const disabledCount = plugins.filter(p => !p.enabled).length;
|
||||
|
||||
const renderPluginCard = (plugin: IEditorPluginMetadata) => (
|
||||
<div key={plugin.name} className={`plugin-card ${plugin.enabled ? 'enabled' : 'disabled'}`}>
|
||||
<div className="plugin-card-header">
|
||||
<div className="plugin-card-icon">
|
||||
{plugin.icon || <Package size={24} />}
|
||||
</div>
|
||||
<div className="plugin-card-info">
|
||||
<div className="plugin-card-title">{plugin.displayName}</div>
|
||||
<div className="plugin-card-version">v{plugin.version}</div>
|
||||
</div>
|
||||
<button
|
||||
className={`plugin-toggle ${plugin.enabled ? 'enabled' : 'disabled'}`}
|
||||
onClick={() => togglePlugin(plugin.name, plugin.enabled)}
|
||||
title={plugin.enabled ? 'Disable plugin' : 'Enable plugin'}
|
||||
>
|
||||
{plugin.enabled ? <CheckCircle size={18} /> : <XCircle size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<div className="plugin-card-description">{plugin.description}</div>
|
||||
)}
|
||||
<div className="plugin-card-footer">
|
||||
<span className="plugin-card-category">
|
||||
{categoryIcons[plugin.category]} {categoryNames[plugin.category]}
|
||||
</span>
|
||||
{plugin.installedAt && (
|
||||
<span className="plugin-card-installed">
|
||||
Installed: {new Date(plugin.installedAt).toLocaleDateString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderPluginList = (plugin: IEditorPluginMetadata) => (
|
||||
<div key={plugin.name} className={`plugin-list-item ${plugin.enabled ? 'enabled' : 'disabled'}`}>
|
||||
<div className="plugin-list-icon">
|
||||
{plugin.icon || <Package size={20} />}
|
||||
</div>
|
||||
<div className="plugin-list-info">
|
||||
<div className="plugin-list-name">
|
||||
{plugin.displayName}
|
||||
<span className="plugin-list-version">v{plugin.version}</span>
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<div className="plugin-list-description">{plugin.description}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="plugin-list-status">
|
||||
{plugin.enabled ? (
|
||||
<span className="status-badge enabled">Enabled</span>
|
||||
) : (
|
||||
<span className="status-badge disabled">Disabled</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="plugin-list-toggle"
|
||||
onClick={() => togglePlugin(plugin.name, plugin.enabled)}
|
||||
title={plugin.enabled ? 'Disable plugin' : 'Enable plugin'}
|
||||
>
|
||||
{plugin.enabled ? 'Disable' : 'Enable'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="plugin-panel">
|
||||
<div className="plugin-toolbar">
|
||||
<div className="plugin-toolbar-left">
|
||||
<div className="plugin-search">
|
||||
<Search size={14} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search plugins..."
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="plugin-toolbar-right">
|
||||
<div className="plugin-stats">
|
||||
<span className="stat-item enabled">
|
||||
<CheckCircle size={14} />
|
||||
{enabledCount} enabled
|
||||
</span>
|
||||
<span className="stat-item disabled">
|
||||
<XCircle size={14} />
|
||||
{disabledCount} disabled
|
||||
</span>
|
||||
</div>
|
||||
<div className="plugin-view-mode">
|
||||
<button
|
||||
className={viewMode === 'list' ? 'active' : ''}
|
||||
onClick={() => setViewMode('list')}
|
||||
title="List view"
|
||||
>
|
||||
<List size={14} />
|
||||
</button>
|
||||
<button
|
||||
className={viewMode === 'grid' ? 'active' : ''}
|
||||
onClick={() => setViewMode('grid')}
|
||||
title="Grid view"
|
||||
>
|
||||
<Grid size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="plugin-content">
|
||||
{plugins.length === 0 ? (
|
||||
<div className="plugin-empty">
|
||||
<Package size={48} />
|
||||
<p>No plugins installed</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="plugin-categories">
|
||||
{Object.entries(pluginsByCategory).map(([category, categoryPlugins]) => {
|
||||
const cat = category as EditorPluginCategory;
|
||||
const isExpanded = expandedCategories.has(cat);
|
||||
|
||||
return (
|
||||
<div key={category} className="plugin-category">
|
||||
<div
|
||||
className="plugin-category-header"
|
||||
onClick={() => toggleCategory(cat)}
|
||||
>
|
||||
<button className="plugin-category-toggle">
|
||||
{isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
</button>
|
||||
<span className="plugin-category-icon">{categoryIcons[cat]}</span>
|
||||
<span className="plugin-category-name">{categoryNames[cat]}</span>
|
||||
<span className="plugin-category-count">
|
||||
{categoryPlugins.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className={`plugin-category-content ${viewMode}`}>
|
||||
{viewMode === 'grid'
|
||||
? categoryPlugins.map(renderPluginCard)
|
||||
: categoryPlugins.map(renderPluginList)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
448
packages/editor-app/src/styles/PluginManagerWindow.css
Normal file
448
packages/editor-app/src/styles/PluginManagerWindow.css
Normal file
@@ -0,0 +1,448 @@
|
||||
.plugin-manager-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.plugin-manager-window {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border-default);
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 1000px;
|
||||
height: 80%;
|
||||
max-height: 700px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.plugin-manager-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
background: var(--color-bg-overlay);
|
||||
border-bottom: 1px solid var(--color-border-default);
|
||||
}
|
||||
|
||||
.plugin-manager-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.plugin-manager-title h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.plugin-manager-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.plugin-manager-close:hover {
|
||||
background: var(--color-bg-hover);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.plugin-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 20px;
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border-default);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.plugin-toolbar-left,
|
||||
.plugin-toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.plugin-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border-default);
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-search input {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 13px;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.plugin-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.plugin-stats .stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.plugin-stats .stat-item.enabled {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.plugin-stats .stat-item.disabled {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-view-mode {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border-default);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.plugin-view-mode button {
|
||||
padding: 6px 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.plugin-view-mode button:hover {
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.plugin-view-mode button.active {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.plugin-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.plugin-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--color-text-secondary);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.plugin-categories {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.plugin-category {
|
||||
background: var(--color-bg-overlay);
|
||||
border: 1px solid var(--color-border-default);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.plugin-category-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: var(--color-bg-elevated);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.plugin-category-header:hover {
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.plugin-category-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.plugin-category-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.plugin-category-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.plugin-category-count {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-bg-overlay);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.plugin-category-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.plugin-category-content.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.plugin-category-content.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Plugin Card (Grid View) */
|
||||
.plugin-card {
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border-default);
|
||||
border-radius: 6px;
|
||||
padding: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.plugin-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.plugin-card.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.plugin-card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.plugin-card-icon {
|
||||
font-size: 24px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.plugin-card-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.plugin-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.plugin-card-version {
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-secondary);
|
||||
transition: all 0.2s;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.plugin-toggle:hover {
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.plugin-toggle.enabled {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.plugin-toggle.disabled {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-card-description {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.plugin-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--color-border-default);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.plugin-card-category {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-card-installed {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
/* Plugin List (List View) */
|
||||
.plugin-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border-default);
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.plugin-list-item:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.plugin-list-item.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.plugin-list-icon {
|
||||
font-size: 20px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.plugin-list-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.plugin-list-name {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.plugin-list-version {
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-list-description {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.plugin-list-status {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.enabled {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.status-badge.disabled {
|
||||
background: var(--color-bg-overlay);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-list-toggle {
|
||||
padding: 6px 14px;
|
||||
background: var(--color-bg-overlay);
|
||||
border: 1px solid var(--color-border-default);
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.plugin-list-toggle:hover {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.plugin-content::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.plugin-content::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-elevated);
|
||||
}
|
||||
|
||||
.plugin-content::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border-default);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.plugin-content::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-text-secondary);
|
||||
}
|
||||
381
packages/editor-app/src/styles/PluginPanel.css
Normal file
381
packages/editor-app/src/styles/PluginPanel.css
Normal file
@@ -0,0 +1,381 @@
|
||||
.plugin-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.plugin-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: var(--color-bg-tertiary);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.plugin-toolbar-left,
|
||||
.plugin-toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.plugin-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-search input {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 12px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.plugin-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.plugin-stats .stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.plugin-stats .stat-item.enabled {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.plugin-stats .stat-item.disabled {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-view-mode {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.plugin-view-mode button {
|
||||
padding: 4px 8px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.plugin-view-mode button:hover {
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.plugin-view-mode button.active {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.plugin-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.plugin-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--color-text-secondary);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.plugin-categories {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.plugin-category {
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.plugin-category-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
background: var(--color-bg-secondary);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.plugin-category-header:hover {
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.plugin-category-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.plugin-category-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.plugin-category-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.plugin-category-count {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-bg-tertiary);
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.plugin-category-content {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.plugin-category-content.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.plugin-category-content.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Plugin Card (Grid View) */
|
||||
.plugin-card {
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.plugin-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.plugin-card.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.plugin-card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.plugin-card-icon {
|
||||
font-size: 24px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.plugin-card-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.plugin-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.plugin-card-version {
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-secondary);
|
||||
transition: all 0.2s;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.plugin-toggle:hover {
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.plugin-toggle.enabled {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.plugin-toggle.disabled {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-card-description {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.plugin-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.plugin-card-category {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-card-installed {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
/* Plugin List (List View) */
|
||||
.plugin-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.plugin-list-item:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.plugin-list-item.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.plugin-list-icon {
|
||||
font-size: 20px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.plugin-list-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.plugin-list-name {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.plugin-list-version {
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-list-description {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.plugin-list-status {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.enabled {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.status-badge.disabled {
|
||||
background: var(--color-bg-tertiary);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.plugin-list-toggle {
|
||||
padding: 6px 12px;
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.plugin-list-toggle:hover {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.plugin-content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.plugin-content::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.plugin-content::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.plugin-content::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-text-secondary);
|
||||
}
|
||||
Reference in New Issue
Block a user