}> {
+ constructor() {
+ super({});
+ this.state = {
+ plugins: [],
+ runningPlugin: ""
+ }
+ }
+
+ 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 }) });
+ }
+
+ private openPlugin(name: string) {
+ const ev = new Event("pluginOpen");
+ ev.data = name;
+ window.__DeckyEvLoop.dispatchEvent(ev);
+ this.setState({ runningPlugin: name, plugins: this.state.plugins })
+ }
+
+ 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()
+ }
+ if (buttons.length == 0) return No plugins...
;
+ return buttons;
+ }
+ }
+}
+
+export default PluginView;
\ No newline at end of file
diff --git a/frontend/src/components/TitleView.tsx b/frontend/src/components/TitleView.tsx
new file mode 100644
index 00000000..e0a8552f
--- /dev/null
+++ b/frontend/src/components/TitleView.tsx
@@ -0,0 +1,39 @@
+import { Button, staticClasses } from "decky-frontend-lib";
+import React from "react"
+import { FaArrowCircleLeft, FaShoppingBag } from "react-icons/fa"
+
+class TitleView extends React.Component<{}, { runningPlugin: string }> {
+ constructor() {
+ super({});
+ this.state = {
+ runningPlugin: ""
+ }
+ }
+
+ componentDidMount() {
+ window.__DeckyEvLoop.addEventListener("pluginOpen", (ev) => this.setState({ runningPlugin: ev.data }));
+ window.__DeckyEvLoop.addEventListener("pluginClose", (_) => this.setState({ runningPlugin: "" }));
+ }
+
+ private openPluginStore() {
+ fetch("http://127.0.0.1:1337/methods/open_plugin_store", {method: "POST"})
+ }
+
+ render() {
+ if (this.state.runningPlugin)
+ return
+
+ {this.state.runningPlugin}
+
+ else
+ return
+ Plugins
+
+
+ }
+}
+
+export default TitleView;
\ No newline at end of file
diff --git a/frontend/src/index.ts b/frontend/src/index.ts
deleted file mode 100644
index 390b83c9..00000000
--- a/frontend/src/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import PluginLoader from './plugin-loader';
-
-declare global {
- interface Window {
- DeckyPluginLoader?: PluginLoader;
- }
-}
-
-if (window.DeckyPluginLoader) {
- window.DeckyPluginLoader?.dismountAll();
-}
-
-window.DeckyPluginLoader = new PluginLoader();
-setTimeout(async () => {
- window.DeckyPluginLoader?.loadAllPlugins();
-}, 5000);
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
new file mode 100644
index 00000000..13118ca3
--- /dev/null
+++ b/frontend/src/index.tsx
@@ -0,0 +1,21 @@
+import PluginLoader from "./plugin-loader"
+
+declare global {
+ interface Window {
+ DeckyPluginLoader: PluginLoader;
+ importDeckyPlugin: Function;
+ syncDeckyPlugins: Function;
+ }
+}
+window.DeckyPluginLoader = new PluginLoader();
+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();
+ for (const plugin of plugins) {
+ window.DeckyPluginLoader?.importPlugin(plugin);
+ }
+}
+
+setTimeout(() => window.syncDeckyPlugins(), 5000);
\ No newline at end of file
diff --git a/frontend/src/plugin-loader.ts b/frontend/src/plugin-loader.ts
deleted file mode 100644
index 9a72ea84..00000000
--- a/frontend/src/plugin-loader.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import Logger from './logger';
-import TabsHook from './tabs-hook';
-
-interface Plugin {
- title: any;
- content: any;
- icon: any;
- onDismount?(): void;
-}
-
-class PluginLoader extends Logger {
- private pluginInstances: Record = {};
- private tabsHook: TabsHook;
- private lock = 0;
-
- constructor() {
- super(PluginLoader.name);
-
- this.log('Initialized');
- this.tabsHook = new TabsHook();
- }
-
- dismountPlugin(name: string) {
- this.log(`Dismounting ${name}`);
- this.pluginInstances[name]?.onDismount?.();
- delete this.pluginInstances[name];
- this.tabsHook.removeById(name);
- }
-
- async loadAllPlugins() {
- this.log('Loading all plugins');
- const plugins = await (await fetch(`http://127.0.0.1:1337/plugins`)).json();
- this.log('Received:', plugins);
-
- return Promise.all(plugins.map((plugin) => this.loadPlugin(plugin.name)));
- }
-
- async loadPlugin(name) {
- this.log('Loading Plugin:', name);
-
- try {
- while (this.lock === 1) {
- await new Promise((resolve) => setTimeout(resolve, 1000));
- }
- this.lock = 1;
-
- if (this.pluginInstances[name]) {
- this.dismountPlugin(name);
- }
-
- const response = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`);
- const code = await response.text();
-
- const pluginAPI = PluginLoader.createPluginAPI(name);
- this.pluginInstances[name] = await eval(code)(pluginAPI);
-
- const { title, icon, content } = this.pluginInstances[name];
- this.tabsHook.add({
- id: name,
- title,
- icon,
- content,
- });
- } catch (e) {
- console.error(e);
- } finally {
- this.lock = 0;
- }
- }
-
- dismountAll() {
- for (const name of Object.keys(this.pluginInstances)) {
- this.dismountPlugin(name);
- }
- }
-
- static createPluginAPI(pluginName) {
- return {
- async callServerMethod(methodName, args = {}) {
- const response = await fetch(`http://127.0.0.1:1337/methods/${methodName}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(args),
- });
-
- return response.json();
- },
- async callPluginMethod(methodName, args = {}) {
- const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- args,
- }),
- });
-
- return response.json();
- },
- fetchNoCors(url, 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) {
- return this.callServerMethod('execute_in_tab', {
- tab,
- run_async: runAsync,
- code,
- });
- },
- injectCssIntoTab(tab, style) {
- return this.callServerMethod('inject_css_into_tab', {
- tab,
- style,
- });
- },
- removeCssFromTab(tab, cssId) {
- return this.callServerMethod('remove_css_from_tab', {
- tab,
- css_id: cssId,
- });
- },
- };
- }
-}
-
-export default PluginLoader;
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
new file mode 100644
index 00000000..cf15e099
--- /dev/null
+++ b/frontend/src/plugin-loader.tsx
@@ -0,0 +1,140 @@
+import Logger from './logger';
+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;
+ }
+}
+class PluginEventTarget extends EventTarget { }
+window.__DeckyEvLoop = new PluginEventTarget();
+
+class PluginLoader extends Logger {
+ private plugins: Plugin[] = [];
+ private tabsHook: TabsHook = new TabsHook();
+
+ constructor() {
+ super(PluginLoader.name);
+ this.log('Initialized');
+ this.tabsHook.add({
+ id: "main",
+ title: ,
+ content: ,
+ icon:
+ });
+ 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 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);
+ this.log(`Loaded ${name}`);
+ const ev = new Event("setPlugins");
+ ev.data = this.plugins;
+ window.__DeckyEvLoop.dispatchEvent(ev);
+ }
+
+ private async importReactPlugin(name: string) {
+ let res = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`);
+ if (res.ok) {
+ let content = await eval(await res.text())(PluginLoader.createPluginAPI(name));
+ this.plugins.push({
+ name: name,
+ icon: content.icon,
+ content: content.content
+ });
+ }
+ else throw new Error(`${name} frontend_bundle not OK`);
+ }
+
+ private async importLegacyPlugin(name: string) {
+ const url = `http://127.0.0.1:1337/plugins/load_main/${name}`;
+ this.plugins.push({
+ name: name,
+ icon: ,
+ content:
+ });
+ }
+
+ static createPluginAPI(pluginName) {
+ return {
+ async callServerMethod(methodName, args = {}) {
+ const response = await fetch(`http://127.0.0.1:1337/methods/${methodName}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(args),
+ });
+
+ return response.json();
+ },
+ async callPluginMethod(methodName, args = {}) {
+ const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ args,
+ }),
+ });
+
+ return response.json();
+ },
+ fetchNoCors(url, 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) {
+ return this.callServerMethod('execute_in_tab', {
+ tab,
+ run_async: runAsync,
+ code,
+ });
+ },
+ injectCssIntoTab(tab, style) {
+ return this.callServerMethod('inject_css_into_tab', {
+ tab,
+ style,
+ });
+ },
+ removeCssFromTab(tab, cssId) {
+ return this.callServerMethod('remove_css_from_tab', {
+ tab,
+ css_id: cssId,
+ });
+ },
+ };
+ }
+}
+
+export default PluginLoader;
\ No newline at end of file
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 00000000..2901f27f
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "outDir": "dist",
+ "module": "ESNext",
+ "target": "ES2020",
+ "jsx": "react-jsx",
+ "declaration": false,
+ "moduleResolution": "node",
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "esModuleInterop": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": true,
+ "strict": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true
+ },
+ "include": ["src"],
+ "exclude": ["node_modules"]
+}