From 4b923c1dc70eaa4a3ca58d9e9f3218785b2fe919 Mon Sep 17 00:00:00 2001 From: marios Date: Thu, 26 May 2022 04:00:18 +0300 Subject: display overhaul, compatibility with legacy plugins, fixes --- frontend/src/components/LegacyPlugin.tsx | 13 +++ frontend/src/components/PluginView.tsx | 40 +++++++++ frontend/src/components/TitleView.tsx | 39 +++++++++ frontend/src/index.ts | 16 ---- frontend/src/index.tsx | 21 +++++ frontend/src/plugin-loader.ts | 131 ----------------------------- frontend/src/plugin-loader.tsx | 140 +++++++++++++++++++++++++++++++ 7 files changed, 253 insertions(+), 147 deletions(-) create mode 100644 frontend/src/components/LegacyPlugin.tsx create mode 100644 frontend/src/components/PluginView.tsx create mode 100644 frontend/src/components/TitleView.tsx delete mode 100644 frontend/src/index.ts create mode 100644 frontend/src/index.tsx delete mode 100644 frontend/src/plugin-loader.ts create mode 100644 frontend/src/plugin-loader.tsx (limited to 'frontend/src') diff --git a/frontend/src/components/LegacyPlugin.tsx b/frontend/src/components/LegacyPlugin.tsx new file mode 100644 index 00000000..86abf2c8 --- /dev/null +++ b/frontend/src/components/LegacyPlugin.tsx @@ -0,0 +1,13 @@ +import React from "react" + +class LegacyPlugin extends React.Component { + constructor(props: object) { + super(props); + } + + render() { + return + } +} + +export default LegacyPlugin; \ No newline at end of file diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx new file mode 100644 index 00000000..27cb386a --- /dev/null +++ b/frontend/src/components/PluginView.tsx @@ -0,0 +1,40 @@ +import { Button } from "decky-frontend-lib"; +import React from "react" + +class PluginView extends React.Component<{}, { runningPlugin: string, plugins: Array }> { + 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 -- cgit v1.2.3