summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml4
-rw-r--r--backend/loader.py34
-rw-r--r--backend/plugin.py12
-rw-r--r--frontend/.gitignore3
-rw-r--r--frontend/package-lock.json18
-rw-r--r--frontend/package.json10
-rw-r--r--frontend/rollup.config.js7
-rw-r--r--frontend/src/components/DeckyState.tsx74
-rw-r--r--frontend/src/components/LegacyPlugin.tsx26
-rw-r--r--frontend/src/components/PluginView.tsx69
-rw-r--r--frontend/src/components/TitleView.tsx49
-rw-r--r--frontend/src/index.tsx18
-rw-r--r--frontend/src/plugin-loader.tsx98
-rw-r--r--frontend/src/plugin.ts6
-rw-r--r--frontend/src/tabs-hook.ts4
15 files changed, 254 insertions, 178 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 537729c5..e6a16430 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,9 +2,9 @@ name: Builder
on:
push:
- branches: [ main ]
+ branches: [ "*" ]
pull_request:
- branches: [ main ]
+ branches: [ "*" ]
permissions:
contents: read
diff --git a/backend/loader.py b/backend/loader.py
index 60b1a901..493e7f10 100644
--- a/backend/loader.py
+++ b/backend/loader.py
@@ -1,4 +1,5 @@
from asyncio import Queue
+from json.decoder import JSONDecodeError
from logging import getLogger
from os import listdir, path
from pathlib import Path
@@ -6,24 +7,22 @@ from traceback import print_exc
from aiohttp import web
from genericpath import exists
-from json.decoder import JSONDecodeError
-from watchdog.events import FileSystemEventHandler
-from watchdog.observers.polling import PollingObserver as Observer
+from watchdog.events import RegexMatchingEventHandler
+from watchdog.observers.inotify import InotifyObserver as Observer
-from injector import inject_to_tab, get_tab
+from injector import get_tab, inject_to_tab
from plugin import PluginWrapper
-class FileChangeHandler(FileSystemEventHandler):
+class FileChangeHandler(RegexMatchingEventHandler):
def __init__(self, queue, plugin_path) -> None:
- super().__init__()
+ super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$'])
self.logger = getLogger("file-watcher")
self.plugin_path = plugin_path
self.queue = queue
def maybe_reload(self, src_path):
plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0]
- self.logger.info(path.join(self.plugin_path, plugin_dir, "plugin.json"))
if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")):
self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
@@ -74,7 +73,8 @@ class Loader:
web.get("/plugins", self.get_plugins),
web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle),
web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call),
-
+ web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_frontend_assets),
+
# The following is legacy plugin code.
web.get("/plugins/load_main/{name}", self.load_plugin_main_view),
web.get("/plugins/plugin_resource/{name}/{path:.+}", self.handle_sub_route),
@@ -85,10 +85,16 @@ class Loader:
plugins = list(self.plugins.values())
return web.json_response([str(i) if not i.legacy else "$LEGACY_"+str(i) for i in plugins])
- async def handle_frontend_bundle(self, request):
+ def handle_frontend_assets(self, request):
plugin = self.plugins[request.match_info["plugin_name"]]
+ file = path.join(self.plugin_path, plugin.plugin_directory, "dist/assets", request.match_info["path"])
+
+ return web.FileResponse(file)
- with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.frontend_bundle), 'r') as bundle:
+ def handle_frontend_bundle(self, request):
+ plugin = self.plugins[request.match_info["plugin_name"]]
+
+ with open(path.join(self.plugin_path, plugin.plugin_directory, "dist/index.js"), 'r') as bundle:
return web.Response(text=bundle.read(), content_type="application/javascript")
def import_plugin(self, file, plugin_directory, refresh=False):
@@ -145,12 +151,12 @@ class Loader:
res["success"] = False
return web.json_response(res)
- """
+ """
The following methods are used to load legacy plugins, which are considered deprecated.
I made the choice to re-add them so that the first iteration/version of the react loader
- can work as a drop-in replacement for the stable branch of the PluginLoader, so that we
+ can work as a drop-in replacement for the stable branch of the PluginLoader, so that we
can introduce it more smoothly and give people the chance to sample the new features even
- without plugin support. They will be removed once legacy plugins are no longer relevant.
+ without plugin support. They will be removed once legacy plugins are no longer relevant.
"""
async def load_plugin_main_view(self, request):
plugin = self.plugins[request.match_info["name"]]
@@ -180,4 +186,4 @@ class Loader:
try:
return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html")
except Exception as e:
- return web.Response(text=str(e), status=400) \ No newline at end of file
+ return web.Response(text=str(e), status=400)
diff --git a/backend/plugin.py b/backend/plugin.py
index 502b35bf..e6cceffd 100644
--- a/backend/plugin.py
+++ b/backend/plugin.py
@@ -10,6 +10,7 @@ from signal import SIGINT, signal
from sys import exit
from time import time
+
class PluginWrapper:
def __init__(self, file, plugin_directory, plugin_path) -> None:
self.file = file
@@ -21,13 +22,10 @@ class PluginWrapper:
json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r"))
- if "frontend_bundle" in json:
- self.frontend_bundle = json["frontend_bundle"]
- self.legacy = False
- else:
- self.main_view_html = json["main_view_html"]
- self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else ""
- self.legacy = True
+ self.legacy = False
+ self.main_view_html = json["main_view_html"] if "main_view_html" in json else ""
+ self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else ""
+ self.legacy = self.main_view_html or self.tile_view_html
self.name = json["name"]
self.author = json["author"]
diff --git a/frontend/.gitignore b/frontend/.gitignore
index c2658d7d..5861baa5 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -1 +1,4 @@
node_modules/
+
+.yalc
+yalc.lock
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 23bdd8db..1605b373 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.0.1",
"license": "GPLV2",
"dependencies": {
- "decky-frontend-lib": "^0.0.2",
+ "decky-frontend-lib": "^0.0.3",
"react-icons": "^4.3.1"
},
"devDependencies": {
@@ -998,12 +998,9 @@
}
},
"node_modules/decky-frontend-lib": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-0.0.2.tgz",
- "integrity": "sha512-XyxSLrkvEjA0oDsPnV30cyMgzqSOKMp3riAkk1e0iEItvt26m4w4iGPD7JO1rtXquWpSEvYB9SCmhlrX/3COXA==",
- "peerDependencies": {
- "react": "16.14.0"
- }
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-0.0.3.tgz",
+ "integrity": "sha512-kOiXyUcxN4wtCsYZ/aMnNdvGL8lqM04Q9JmbzE9ykdbKq1AStn3HxE6dzOL4DukHahM/dyifLt7tTN93bDE0SA=="
},
"node_modules/deepmerge": {
"version": "4.2.2",
@@ -2949,10 +2946,9 @@
}
},
"decky-frontend-lib": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-0.0.2.tgz",
- "integrity": "sha512-XyxSLrkvEjA0oDsPnV30cyMgzqSOKMp3riAkk1e0iEItvt26m4w4iGPD7JO1rtXquWpSEvYB9SCmhlrX/3COXA==",
- "requires": {}
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-0.0.3.tgz",
+ "integrity": "sha512-kOiXyUcxN4wtCsYZ/aMnNdvGL8lqM04Q9JmbzE9ykdbKq1AStn3HxE6dzOL4DukHahM/dyifLt7tTN93bDE0SA=="
},
"deepmerge": {
"version": "4.2.2",
diff --git a/frontend/package.json b/frontend/package.json
index 0489b0f9..51bed2db 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,10 +10,6 @@
"format": "prettier -c src -w"
},
"devDependencies": {
- "husky": "^8.0.1",
- "import-sort-style-module": "^6.0.0",
- "prettier": "^2.6.2",
- "prettier-plugin-import-sort": "^0.0.7",
"@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.2.1",
@@ -21,6 +17,10 @@
"@rollup/plugin-typescript": "^8.3.2",
"@types/react": "16.14.0",
"@types/webpack": "^5.28.0",
+ "husky": "^8.0.1",
+ "import-sort-style-module": "^6.0.0",
+ "prettier": "^2.6.2",
+ "prettier-plugin-import-sort": "^0.0.7",
"react": "16.14.0",
"react-dom": "16.14.0",
"rollup": "^2.70.2"
@@ -31,7 +31,7 @@
}
},
"dependencies": {
- "decky-frontend-lib": "^0.0.2",
+ "decky-frontend-lib": "^0.0.3",
"react-icons": "^4.3.1"
}
}
diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js
index 9a259df2..b253b679 100644
--- a/frontend/rollup.config.js
+++ b/frontend/rollup.config.js
@@ -17,8 +17,13 @@ export default defineConfig({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],
+ external: ["react", "react-dom"],
output: {
file: '../backend/static/plugin-loader.iife.js',
+ globals: {
+ react: 'SP_REACT',
+ 'react-dom': 'SP_REACTDOM',
+ },
format: 'iife',
},
-}); \ No newline at end of file
+});
diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx
new file mode 100644
index 00000000..cbeeb5b4
--- /dev/null
+++ b/frontend/src/components/DeckyState.tsx
@@ -0,0 +1,74 @@
+import { FC, createContext, useContext, useEffect, useState } from 'react';
+
+import { Plugin } from '../plugin';
+
+interface PublicDeckyState {
+ plugins: Plugin[];
+ activePlugin: Plugin | null;
+}
+
+export class DeckyState {
+ private _plugins: Plugin[] = [];
+ private _activePlugin: Plugin | null = null;
+
+ public eventBus = new EventTarget();
+
+ publicState(): PublicDeckyState {
+ return { plugins: this._plugins, activePlugin: this._activePlugin };
+ }
+
+ setPlugins(plugins: Plugin[]) {
+ this._plugins = plugins;
+ this.notifyUpdate();
+ }
+
+ setActivePlugin(name: string) {
+ this._activePlugin = this._plugins.find((plugin) => plugin.name === name) ?? null;
+ this.notifyUpdate();
+ }
+
+ closeActivePlugin() {
+ this._activePlugin = null;
+ this.notifyUpdate();
+ }
+
+ private notifyUpdate() {
+ this.eventBus.dispatchEvent(new Event('update'));
+ }
+}
+
+interface DeckyStateContext extends PublicDeckyState {
+ setActivePlugin(name: string): void;
+ closeActivePlugin(): void;
+}
+
+const DeckyStateContext = createContext<DeckyStateContext>(null as any);
+
+export const useDeckyState = () => useContext(DeckyStateContext);
+
+interface Props {
+ deckyState: DeckyState;
+}
+
+export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) => {
+ const [publicDeckyState, setPublicDeckyState] = useState<PublicDeckyState>({ ...deckyState.publicState() });
+
+ useEffect(() => {
+ function onUpdate() {
+ setPublicDeckyState({ ...deckyState.publicState() });
+ }
+
+ deckyState.eventBus.addEventListener('update', onUpdate);
+
+ return () => deckyState.eventBus.removeEventListener('update', onUpdate);
+ }, []);
+
+ const setActivePlugin = (name: string) => deckyState.setActivePlugin(name);
+ const closeActivePlugin = () => deckyState.closeActivePlugin();
+
+ return (
+ <DeckyStateContext.Provider value={{ ...publicDeckyState, setActivePlugin, closeActivePlugin }}>
+ {children}
+ </DeckyStateContext.Provider>
+ );
+};
diff --git a/frontend/src/components/LegacyPlugin.tsx b/frontend/src/components/LegacyPlugin.tsx
index 86abf2c8..40f9e85f 100644
--- a/frontend/src/components/LegacyPlugin.tsx
+++ b/frontend/src/components/LegacyPlugin.tsx
@@ -1,13 +1,21 @@
-import React from "react"
+import { VFC } from 'react';
-class LegacyPlugin extends React.Component {
- constructor(props: object) {
- super(props);
- }
+// class LegacyPlugin extends React.Component {
+// constructor(props: object) {
+// super(props);
+// }
- render() {
- return <iframe style={{ border: 'none', width: '100%', height: '100%' }} src={this.props.url}></iframe>
- }
+// render() {
+// return <iframe style={{ border: 'none', width: '100%', height: '100%' }} src={this.props.url}></iframe>
+// }
+// }
+
+interface Props {
+ url: string;
}
-export default LegacyPlugin; \ No newline at end of file
+const LegacyPlugin: VFC<Props> = () => {
+ return <div>LegacyPlugin Hello World</div>;
+};
+
+export default LegacyPlugin;
diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx
index 27cb386a..b3640395 100644
--- a/frontend/src/components/PluginView.tsx
+++ b/frontend/src/components/PluginView.tsx
@@ -1,40 +1,39 @@
-import { Button } from "decky-frontend-lib";
-import React from "react"
+import { ButtonItem, DialogButton, PanelSection, PanelSectionRow } from 'decky-frontend-lib';
+import { VFC } from 'react';
+import { FaArrowLeft } from 'react-icons/fa';
-class PluginView extends React.Component<{}, { runningPlugin: string, plugins: Array<any> }> {
- constructor() {
- super({});
- this.state = {
- plugins: [],
- runningPlugin: ""
- }
- }
+import { useDeckyState } from './DeckyState';
- componentDidMount() {
- window.__DeckyEvLoop.addEventListener("pluginClose", (_) => { this.setState({ runningPlugin: "", plugins: this.state.plugins }) });
- window.__DeckyEvLoop.addEventListener("setPlugins", (ev) => { console.log(ev); this.setState({ plugins: ev.data, runningPlugin: this.state.runningPlugin }) });
- }
+const PluginView: VFC = () => {
+ const { plugins, activePlugin, setActivePlugin, closeActivePlugin } = useDeckyState();
- private openPlugin(name: string) {
- const ev = new Event("pluginOpen");
- ev.data = name;
- window.__DeckyEvLoop.dispatchEvent(ev);
- this.setState({ runningPlugin: name, plugins: this.state.plugins })
- }
+ if (activePlugin) {
+ return (
+ <div>
+ <div style={{ position: 'absolute', top: '3px', left: '16px', zIndex: 20 }}>
+ <DialogButton style={{ minWidth: 0, padding: '10px 12px' }} onClick={closeActivePlugin}>
+ <FaArrowLeft style={{ display: 'block' }} />
+ </DialogButton>
+ </div>
+ {activePlugin.content}
+ </div>
+ );
+ }
- render() {
- if (this.state.runningPlugin) {
- return this.state.plugins.find(x => x.name == this.state.runningPlugin).content;
- }
- else {
- let buttons = [];
- for (const plugin of this.state.plugins) {
- buttons.push(<Button layout="below" onClick={(_) => this.openPlugin(plugin.name)}>{plugin.icon}{plugin.name}</Button>)
- }
- if (buttons.length == 0) return <div className='staticClasses.Text'>No plugins...</div>;
- return buttons;
- }
- }
-}
+ return (
+ <PanelSection>
+ {plugins.map(({ name, icon }) => (
+ <PanelSectionRow key={name}>
+ <ButtonItem layout="below" onClick={() => setActivePlugin(name)}>
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <div>{icon}</div>
+ <div>{name}</div>
+ </div>
+ </ButtonItem>
+ </PanelSectionRow>
+ ))}
+ </PanelSection>
+ );
+};
-export default PluginView; \ No newline at end of file
+export default PluginView;
diff --git a/frontend/src/components/TitleView.tsx b/frontend/src/components/TitleView.tsx
index e0a8552f..4b4a6825 100644
--- a/frontend/src/components/TitleView.tsx
+++ b/frontend/src/components/TitleView.tsx
@@ -1,39 +1,20 @@
-import { Button, staticClasses } from "decky-frontend-lib";
-import React from "react"
-import { FaArrowCircleLeft, FaShoppingBag } from "react-icons/fa"
+import { staticClasses } from 'decky-frontend-lib';
+import { VFC } from 'react';
-class TitleView extends React.Component<{}, { runningPlugin: string }> {
- constructor() {
- super({});
- this.state = {
- runningPlugin: ""
- }
- }
+import { useDeckyState } from './DeckyState';
- componentDidMount() {
- window.__DeckyEvLoop.addEventListener("pluginOpen", (ev) => this.setState({ runningPlugin: ev.data }));
- window.__DeckyEvLoop.addEventListener("pluginClose", (_) => this.setState({ runningPlugin: "" }));
- }
+const TitleView: VFC = () => {
+ const { activePlugin } = useDeckyState();
- private openPluginStore() {
- fetch("http://127.0.0.1:1337/methods/open_plugin_store", {method: "POST"})
- }
+ if (activePlugin === null) {
+ return <div className={staticClasses.Title}>Decky</div>;
+ }
- render() {
- if (this.state.runningPlugin)
- return <div className={staticClasses.Title}>
- <Button bottomSeparator={false} onClick={(_) => {
- window.__DeckyEvLoop.dispatchEvent(new Event("pluginClose"));
- this.setState({ runningPlugin: "" });
- }}><FaArrowCircleLeft /></Button>
- {this.state.runningPlugin}
- </div>
- else
- return <div className={staticClasses.Title}>
- Plugins
- <Button bottomSeparator={false} onClick={ (_) => this.openPluginStore() }><FaShoppingBag /></Button>
- </div>
- }
-}
+ return (
+ <div className={staticClasses.Title} style={{ paddingLeft: '60px' }}>
+ {activePlugin.name}
+ </div>
+ );
+};
-export default TitleView; \ No newline at end of file
+export default TitleView;
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 13118ca3..89194777 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -1,4 +1,4 @@
-import PluginLoader from "./plugin-loader"
+import PluginLoader from './plugin-loader';
declare global {
interface Window {
@@ -7,15 +7,19 @@ declare global {
syncDeckyPlugins: Function;
}
}
+
+window.DeckyPluginLoader?.dismountAll();
+
window.DeckyPluginLoader = new PluginLoader();
-window.importDeckyPlugin = function(name: string) {
+window.importDeckyPlugin = function (name: string) {
window.DeckyPluginLoader?.importPlugin(name);
-}
-window.syncDeckyPlugins = async function() {
- const plugins = await (await fetch("http://127.0.0.1:1337/plugins")).json();
+};
+
+window.syncDeckyPlugins = async function () {
+ const plugins = await (await fetch('http://127.0.0.1:1337/plugins')).json();
for (const plugin of plugins) {
window.DeckyPluginLoader?.importPlugin(plugin);
}
-}
+};
-setTimeout(() => window.syncDeckyPlugins(), 5000); \ No newline at end of file
+setTimeout(() => window.syncDeckyPlugins(), 5000);
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index cf15e099..ddb92542 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -1,64 +1,61 @@
+import { FaPlug } from 'react-icons/fa';
+
+import { DeckyState, DeckyStateContextProvider } from './components/DeckyState';
+import LegacyPlugin from './components/LegacyPlugin';
+import PluginView from './components/PluginView';
+import TitleView from './components/TitleView';
import Logger from './logger';
+import { Plugin } from './plugin';
import TabsHook from './tabs-hook';
-import { FaPlug } from "react-icons/fa";
-
-import PluginView from "./components/PluginView";
-import TitleView from "./components/TitleView";
-import LegacyPlugin from "./components/LegacyPlugin"
-
-interface Plugin {
- name: any;
- content: any;
- icon: any;
- onDismount?(): void;
-}
declare global {
- interface Window {
- __DeckyEvLoop: PluginEventTarget;
- __DeckyRunningPlugin: string;
- }
+ interface Window {}
}
-class PluginEventTarget extends EventTarget { }
-window.__DeckyEvLoop = new PluginEventTarget();
class PluginLoader extends Logger {
private plugins: Plugin[] = [];
private tabsHook: TabsHook = new TabsHook();
+ private deckyState: DeckyState = new DeckyState();
constructor() {
super(PluginLoader.name);
this.log('Initialized');
+
this.tabsHook.add({
- id: "main",
- title: <TitleView />,
- content: <PluginView />,
- icon: <FaPlug />
+ id: 'main',
+ title: (
+ <DeckyStateContextProvider deckyState={this.deckyState}>
+ <TitleView />
+ </DeckyStateContextProvider>
+ ),
+ content: (
+ <DeckyStateContextProvider deckyState={this.deckyState}>
+ <PluginView />
+ </DeckyStateContextProvider>
+ ),
+ icon: <FaPlug />,
});
- SteamClient.Input.RegisterForControllerInputMessages(this.handleBack);
- window.__DeckyEvLoop.addEventListener("pluginOpen", (x) => window.__DeckyRunningPlugin = x.data);
- window.__DeckyEvLoop.addEventListener("pluginClose", (_) => window.__DeckyRunningPlugin = "");
}
- private handleBack(ev) {
- const e = ev[0];
- if (e.strActionName == "B" && window.__DeckyRunningPlugin)
- window.__DeckyEvLoop.dispatchEvent(new Event("pluginClose"));
+ public dismountAll() {
+ for (const plugin of this.plugins) {
+ this.log(`Dismounting ${plugin.name}`);
+ plugin.onDismount?.();
+ }
}
public async importPlugin(name: string) {
this.log(`Trying to load ${name}`);
- let find = this.plugins.find(x => x.name == name);
- if (find)
- this.plugins.splice(this.plugins.indexOf(find), 1);
- if (name.startsWith("$LEGACY_"))
- this.importLegacyPlugin(name.replace("$LEGACY_", ""));
- else
- this.importReactPlugin(name);
+ let find = this.plugins.find((x) => x.name == name);
+ if (find) this.plugins.splice(this.plugins.indexOf(find), 1);
+ if (name.startsWith('$LEGACY_')) {
+ await this.importLegacyPlugin(name.replace('$LEGACY_', ''));
+ } else {
+ await this.importReactPlugin(name);
+ }
this.log(`Loaded ${name}`);
- const ev = new Event("setPlugins");
- ev.data = this.plugins;
- window.__DeckyEvLoop.dispatchEvent(ev);
+
+ this.deckyState.setPlugins(this.plugins);
}
private async importReactPlugin(name: string) {
@@ -68,10 +65,9 @@ class PluginLoader extends Logger {
this.plugins.push({
name: name,
icon: content.icon,
- content: content.content
+ content: content.content,
});
- }
- else throw new Error(`${name} frontend_bundle not OK`);
+ } else throw new Error(`${name} frontend_bundle not OK`);
}
private async importLegacyPlugin(name: string) {
@@ -79,13 +75,13 @@ class PluginLoader extends Logger {
this.plugins.push({
name: name,
icon: <FaPlug />,
- content: <LegacyPlugin url={ url } />
+ content: <LegacyPlugin url={url} />,
});
}
- static createPluginAPI(pluginName) {
+ static createPluginAPI(pluginName: string) {
return {
- async callServerMethod(methodName, args = {}) {
+ async callServerMethod(methodName: string, args = {}) {
const response = await fetch(`http://127.0.0.1:1337/methods/${methodName}`, {
method: 'POST',
headers: {
@@ -96,7 +92,7 @@ class PluginLoader extends Logger {
return response.json();
},
- async callPluginMethod(methodName, args = {}) {
+ async callPluginMethod(methodName: string, args = {}) {
const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, {
method: 'POST',
headers: {
@@ -109,25 +105,25 @@ class PluginLoader extends Logger {
return response.json();
},
- fetchNoCors(url, request: any = {}) {
+ fetchNoCors(url: string, request: any = {}) {
let args = { method: 'POST', headers: {}, body: '' };
const req = { ...args, ...request, url, data: request.body };
return this.callServerMethod('http_request', req);
},
- executeInTab(tab, runAsync, code) {
+ executeInTab(tab: string, runAsync: boolean, code: string) {
return this.callServerMethod('execute_in_tab', {
tab,
run_async: runAsync,
code,
});
},
- injectCssIntoTab(tab, style) {
+ injectCssIntoTab(tab: string, style: string) {
return this.callServerMethod('inject_css_into_tab', {
tab,
style,
});
},
- removeCssFromTab(tab, cssId) {
+ removeCssFromTab(tab: string, cssId: any) {
return this.callServerMethod('remove_css_from_tab', {
tab,
css_id: cssId,
@@ -137,4 +133,4 @@ class PluginLoader extends Logger {
}
}
-export default PluginLoader; \ No newline at end of file
+export default PluginLoader;
diff --git a/frontend/src/plugin.ts b/frontend/src/plugin.ts
new file mode 100644
index 00000000..2780d679
--- /dev/null
+++ b/frontend/src/plugin.ts
@@ -0,0 +1,6 @@
+export interface Plugin {
+ name: any;
+ content: any;
+ icon: any;
+ onDismount?(): void;
+}
diff --git a/frontend/src/tabs-hook.ts b/frontend/src/tabs-hook.ts
index 17f41d91..dd013844 100644
--- a/frontend/src/tabs-hook.ts
+++ b/frontend/src/tabs-hook.ts
@@ -9,7 +9,7 @@ declare global {
}
}
-const isTabsArray = (tabs) => {
+const isTabsArray = (tabs: any) => {
const length = tabs.length;
return length === 7 && tabs[length - 1]?.key === 6 && tabs[length - 1]?.tab;
};
@@ -35,7 +35,7 @@ class TabsHook extends Logger {
const filter = Array.prototype.__filter ?? Array.prototype.filter;
Array.prototype.__filter = filter;
- Array.prototype.filter = function (...args) {
+ Array.prototype.filter = function (...args: any[]) {
if (isTabsArray(this)) {
self.render(this);
}