[skip ci] Feat: Hot reload for plugins
This commit is contained in:
+2
-1
@@ -11,4 +11,5 @@ src/plugins/*/*\[*\]*.ts
|
|||||||
.dist
|
.dist
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist
|
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",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "concurrently \"npm run dev\" \"npm run proxy\"",
|
"start": "node importPlugins.js && concurrently \"npm run dev\" \"npm run proxy\"",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"proxy": "node proxy_server.js",
|
"proxy": "node proxy_server.js",
|
||||||
"test": "exit 0",
|
"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
|
http
|
||||||
.createServer(function (req, res) {
|
.createServer(function (req, res) {
|
||||||
const path = req.url.replace(/^\//, '');
|
const path = req.url.replace(/^\//, '');
|
||||||
if (path === 'cookies') {
|
if (path === 'cookies') {
|
||||||
cookiesHandler(req, res);
|
cookiesHandler(req, res);
|
||||||
} else if (path === 'pluginPaths') {
|
|
||||||
pluginPathsHandler(req, res);
|
|
||||||
} else {
|
} else {
|
||||||
const _url = new URL(path);
|
const _url = new URL(path);
|
||||||
for (const _header of disAllowedHeaders) {
|
for (const _header of disAllowedHeaders) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { FixedSizeList, ListChildComponentProps } from 'react-window';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { AppState } from '@redux/store';
|
import { AppState } from '@redux/store';
|
||||||
import { selectPlugin } from '@redux/pluginSlice';
|
import { selectPlugin } from '@redux/pluginSlice';
|
||||||
|
import usePlugin from '@hooks/usePlugin';
|
||||||
|
|
||||||
const resovleIcon = (iconPath: string) => {
|
const resovleIcon = (iconPath: string) => {
|
||||||
return '/icons/' + iconPath;
|
return '/icons/' + iconPath;
|
||||||
@@ -30,9 +31,7 @@ export default function () {
|
|||||||
const [searchedPlugins, setSearchPlugins] = useState<Plugin.PluginBase[]>([]);
|
const [searchedPlugins, setSearchPlugins] = useState<Plugin.PluginBase[]>([]);
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
const [listVisible, setListVisible] = useState(false);
|
const [listVisible, setListVisible] = useState(false);
|
||||||
const selectedPlugin = useSelector(
|
const plugin = usePlugin();
|
||||||
(state: AppState) => state.plugin.selected,
|
|
||||||
);
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const plugins = searchPlugins(keyword);
|
const plugins = searchPlugins(keyword);
|
||||||
@@ -128,15 +127,15 @@ export default function () {
|
|||||||
Language
|
Language
|
||||||
</Button>
|
</Button>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
{selectedPlugin ? (
|
{plugin ? (
|
||||||
<Box sx={{ pl: 2, display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ pl: 2, display: 'flex', alignItems: 'center' }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{ mr: 1 }}
|
sx={{ mr: 1 }}
|
||||||
src={resovleIcon(selectedPlugin.icon)}
|
src={resovleIcon(plugin.icon)}
|
||||||
variant="square"
|
variant="square"
|
||||||
/>
|
/>
|
||||||
<a href={selectedPlugin.site} target="_blank">
|
<a href={plugin.site} target="_blank">
|
||||||
{selectedPlugin.name} - v{selectedPlugin.version}
|
{plugin.name} - v{plugin.version}
|
||||||
</a>
|
</a>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
Vendored
+1
-4
@@ -1,4 +1 @@
|
|||||||
declare module '*.jpg';
|
/// <reference types="vite/client" />
|
||||||
declare module '*.png';
|
|
||||||
declare module '*.svg';
|
|
||||||
declare module '*.jpeg';
|
|
||||||
|
|||||||
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 React, { ReactNode, useEffect } from 'react';
|
||||||
import DialogProvider from './DialogProvider';
|
import DialogProvider from './DialogProvider';
|
||||||
import { Backdrop, Box, CircularProgress, Container } from '@mui/material';
|
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 { AppState } from '@redux/store';
|
||||||
import { loadPlugins } from './plugins';
|
|
||||||
import { hideLoading } from '@redux/overlaySlice';
|
|
||||||
|
|
||||||
export default function AppProvider() {
|
export default function AppProvider() {
|
||||||
const loading = useSelector((state: AppState) => state.overlay.loading);
|
const loading = useSelector((state: AppState) => state.overlay.loading);
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadPlugins().then(() => dispatch(hideLoading()));
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<Container sx={{ position: 'absolute' }}>
|
<Container sx={{ position: 'absolute' }}>
|
||||||
<Backdrop
|
<Backdrop
|
||||||
|
|||||||
+1
-16
@@ -1,19 +1,4 @@
|
|||||||
import { Plugin } from '@typings/plugin';
|
import plugins from '../plugins';
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const searchPlugins = (keyword: string) => {
|
export const searchPlugins = (keyword: string) => {
|
||||||
return plugins.filter(
|
return plugins.filter(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
loading: true,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const overlaySlice = createSlice({
|
export const overlaySlice = createSlice({
|
||||||
|
|||||||
Reference in New Issue
Block a user