[skip ci] Feat: Hot reload for plugins

This commit is contained in:
nyagami
2024-06-17 22:41:24 +07:00
parent 379f511954
commit 829eab9983
10 changed files with 41 additions and 58 deletions
+2 -1
View File
@@ -11,4 +11,5 @@ src/plugins/*/*\[*\]*.ts
.dist
.DS_Store
dist
total.svg
total.svg
src/plugins/index.ts
+25
View File
@@ -0,0 +1,25 @@
import path from 'path';
import fs from 'fs';
let content = `import { Plugin } from '@typings/plugin';\n`;
let pluginCounter = 0;
const PLUGIN_DIR = 'src/plugins';
fs.readdirSync(PLUGIN_DIR)
.filter(f => !f.includes('index'))
.forEach(langName => {
const LANG_DIR = PLUGIN_DIR + '/' + langName;
fs.readdirSync(LANG_DIR)
.filter(f => !f.includes('broken') && !f.startsWith('.'))
.forEach(pluginName => {
content += `import p_${pluginCounter} from './${langName}/${pluginName.replace(/\.ts$/, '')}';\n`;
pluginCounter += 1;
});
});
content += `\nconst PLUGINS: Plugin.PluginBase[] = [${Array(pluginCounter)
.fill()
.map((v, index) => 'p_' + index.toString())
.join(', ')}];\nexport default PLUGINS`;
fs.writeFileSync('./src/plugins/index.ts', content, { encoding: 'utf-8' });
+1 -1
View File
@@ -5,7 +5,7 @@
"main": "index.js",
"type": "module",
"scripts": {
"start": "concurrently \"npm run dev\" \"npm run proxy\"",
"start": "node importPlugins.js && concurrently \"npm run dev\" \"npm run proxy\"",
"dev": "vite",
"proxy": "node proxy_server.js",
"test": "exit 0",
-20
View File
@@ -86,31 +86,11 @@ const cookiesHandler = (req, res) => {
});
};
const pluginPathsHandler = (req, res) => {
const pluginsDir = 'src/plugins';
const pluginPaths = [];
const langNames = fs.readdirSync(pluginsDir);
langNames.forEach(langName => {
const langDir = pluginsDir + '/' + langName;
const pluginNames = fs.readdirSync(langDir);
pluginNames.forEach(pluginName => {
if (!pluginName.startsWith('.') && !pluginName.includes('broken')) {
pluginPaths.push(langName + '/' + pluginName);
}
});
});
res.setHeader('Access-Control-Allow-Origin', CLIENT_HOST);
res.setHeader('Access-Control-Allow-Credentials', true);
res.end(JSON.stringify(pluginPaths));
};
http
.createServer(function (req, res) {
const path = req.url.replace(/^\//, '');
if (path === 'cookies') {
cookiesHandler(req, res);
} else if (path === 'pluginPaths') {
pluginPathsHandler(req, res);
} else {
const _url = new URL(path);
for (const _header of disAllowedHeaders) {
+6 -7
View File
@@ -21,6 +21,7 @@ import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { useDispatch, useSelector } from 'react-redux';
import { AppState } from '@redux/store';
import { selectPlugin } from '@redux/pluginSlice';
import usePlugin from '@hooks/usePlugin';
const resovleIcon = (iconPath: string) => {
return '/icons/' + iconPath;
@@ -30,9 +31,7 @@ export default function () {
const [searchedPlugins, setSearchPlugins] = useState<Plugin.PluginBase[]>([]);
const [keyword, setKeyword] = useState('');
const [listVisible, setListVisible] = useState(false);
const selectedPlugin = useSelector(
(state: AppState) => state.plugin.selected,
);
const plugin = usePlugin();
const dispatch = useDispatch();
useEffect(() => {
const plugins = searchPlugins(keyword);
@@ -128,15 +127,15 @@ export default function () {
Language
</Button>
</InputAdornment>
{selectedPlugin ? (
{plugin ? (
<Box sx={{ pl: 2, display: 'flex', alignItems: 'center' }}>
<Avatar
sx={{ mr: 1 }}
src={resovleIcon(selectedPlugin.icon)}
src={resovleIcon(plugin.icon)}
variant="square"
/>
<a href={selectedPlugin.site} target="_blank">
{selectedPlugin.name} - v{selectedPlugin.version}
<a href={plugin.site} target="_blank">
{plugin.name} - v{plugin.version}
</a>
</Box>
) : null}
+1 -4
View File
@@ -1,4 +1 @@
declare module '*.jpg';
declare module '*.png';
declare module '*.svg';
declare module '*.jpeg';
/// <reference types="vite/client" />
+3
View File
@@ -0,0 +1,3 @@
import { Plugin } from '@typings/plugin';
export default [] as Plugin.PluginBase[];
+1 -8
View File
@@ -1,18 +1,11 @@
import React, { ReactNode, useEffect } from 'react';
import DialogProvider from './DialogProvider';
import { Backdrop, Box, CircularProgress, Container } from '@mui/material';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector } from 'react-redux';
import { AppState } from '@redux/store';
import { loadPlugins } from './plugins';
import { hideLoading } from '@redux/overlaySlice';
export default function AppProvider() {
const loading = useSelector((state: AppState) => state.overlay.loading);
const dispatch = useDispatch();
useEffect(() => {
loadPlugins().then(() => dispatch(hideLoading()));
}, []);
return (
<Container sx={{ position: 'absolute' }}>
<Backdrop
+1 -16
View File
@@ -1,19 +1,4 @@
import { Plugin } from '@typings/plugin';
const plugins: Plugin.PluginBase[] = [];
export const loadPlugins = async () => {
const paths = await fetch('pluginPaths').then(res => res.json());
const fetchedPlugins: Plugin.PluginBase[] = [];
for (const path of paths) {
const relativePath = '../plugins/' + path;
const plugin: Plugin.PluginBase = (
await import(/* @vite-ignore */ relativePath)
).default;
fetchedPlugins.push(plugin);
}
plugins.splice(0, plugins.length, ...fetchedPlugins);
};
import plugins from '../plugins';
export const searchPlugins = (keyword: string) => {
return plugins.filter(
+1 -1
View File
@@ -1,7 +1,7 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
loading: true,
loading: false,
};
export const overlaySlice = createSlice({