feat(plugins): implement plugin loading and management system

- Added PluginLoader component to dynamically load and initialize plugins.
- Created Plugins component for managing installed and marketplace plugins.
- Introduced PluginAPI for plugin interaction and settings management.
- Defined types for plugin metadata, installed plugins, and plugin index.
- Implemented settings rendering for plugins based on their schema.
- Integrated marketplace plugin fetching and installation logic.
- Added support for enabling/disabling and uninstalling plugins.
This commit is contained in:
2026-04-18 02:55:44 +10:00
parent 9eb5e4fa32
commit d1d3033c15
13 changed files with 2674 additions and 49 deletions

View File

@@ -4,6 +4,7 @@ import { onDarkFontWeight, onLightFontWeight } from '../../config.css';
import { butterTheme, catppuccinMochaTheme, darkTheme, discordTheme, discordDarkerTheme, mochaTheme, silverTheme, twilightTheme } from '../../colors.css';
import { settingsAtom } from '../state/settings';
import { useSetting } from '../state/hooks/settings';
import { pluginRegistry } from '../features/settings/plugins/PluginAPI';
export enum ThemeKind {
Light = 'light',
@@ -64,14 +65,51 @@ export const CatppuccinMochaTheme: Theme = {
};
export const useThemes = (): Theme[] => {
const themes: Theme[] = useMemo(() => [LightTheme, SilverTheme, DarkTheme, ButterTheme, DiscordTheme, DiscordDarkerTheme, TwilightTheme, MochaTheme, CatppuccinMochaTheme], []);
const [pluginThemesUpdate, setPluginThemesUpdate] = useState(0);
// Force update when plugins change (this is a simple approach)
useEffect(() => {
const interval = setInterval(() => {
setPluginThemesUpdate(prev => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
const builtInThemes: Theme[] = useMemo(() => [
LightTheme, SilverTheme, DarkTheme, ButterTheme,
DiscordTheme, DiscordDarkerTheme, TwilightTheme,
MochaTheme, CatppuccinMochaTheme
], []);
// Convert plugin themes to Theme format
const pluginThemes: Theme[] = useMemo(() => {
const themes = pluginRegistry.getPluginThemes();
return themes.map(pluginTheme => ({
id: pluginTheme.id,
kind: pluginTheme.kind === 'dark' ? ThemeKind.Dark : ThemeKind.Light,
classNames: [
pluginTheme.className,
pluginTheme.kind === 'dark' ? onDarkFontWeight : onLightFontWeight,
pluginTheme.kind === 'dark' ? 'prism-dark' : 'prism-light'
]
}));
}, [pluginThemesUpdate]);
return themes;
return [...builtInThemes, ...pluginThemes];
};
export const useThemeNames = (): Record<string, string> =>
useMemo(
() => ({
export const useThemeNames = (): Record<string, string> => {
const [pluginThemesUpdate, setPluginThemesUpdate] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setPluginThemesUpdate(prev => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return useMemo(() => {
const builtInNames = {
[LightTheme.id]: 'Light',
[SilverTheme.id]: 'Silver',
[DarkTheme.id]: 'Dark',
@@ -81,9 +119,17 @@ export const useThemeNames = (): Record<string, string> =>
[TwilightTheme.id]: 'Twilight',
[MochaTheme.id]: 'Mocha',
[CatppuccinMochaTheme.id]: 'Catppuccin Mocha',
}),
[]
);
};
const pluginThemes = pluginRegistry.getPluginThemes();
const pluginNames = pluginThemes.reduce((acc, theme) => {
acc[theme.id] = theme.name;
return acc;
}, {} as Record<string, string>);
return { ...builtInNames, ...pluginNames };
}, [pluginThemesUpdate]);
};
export const useSystemThemeKind = (): ThemeKind => {
const darkModeQueryList = useMemo(() => window.matchMedia('(prefers-color-scheme: dark)'), []);