[skip ci] Feat: Hot reload for plugins
This commit is contained in:
+2
-1
@@ -11,4 +11,5 @@ src/plugins/*/*\[*\]*.ts
|
||||
.dist
|
||||
.DS_Store
|
||||
dist
|
||||
total.svg
|
||||
total.svg
|
||||
src/plugins/index.ts
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
|
||||
Vendored
+1
-4
@@ -1,4 +1 @@
|
||||
declare module '*.jpg';
|
||||
declare module '*.png';
|
||||
declare module '*.svg';
|
||||
declare module '*.jpeg';
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import { Plugin } from '@typings/plugin';
|
||||
|
||||
export default [] as Plugin.PluginBase[];
|
||||
@@ -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
@@ -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,7 +1,7 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
loading: true,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
export const overlaySlice = createSlice({
|
||||
|
||||
Reference in New Issue
Block a user