summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authormarios <marios8543@gmail.com>2022-05-26 04:00:18 +0300
committermarios <marios8543@gmail.com>2022-05-26 04:00:18 +0300
commit4b923c1dc70eaa4a3ca58d9e9f3218785b2fe919 (patch)
tree3394a7e752b61bdfa16b1a7f50842c4e1dbc0972 /frontend/src
parent74438a31458af8bddd08d90eacc6d63677bab844 (diff)
downloaddecky-loader-4b923c1dc70eaa4a3ca58d9e9f3218785b2fe919.tar.gz
decky-loader-4b923c1dc70eaa4a3ca58d9e9f3218785b2fe919.zip
display overhaul, compatibility with legacy plugins, fixes
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/LegacyPlugin.tsx13
-rw-r--r--frontend/src/components/PluginView.tsx40
-rw-r--r--frontend/src/components/TitleView.tsx39
-rw-r--r--frontend/src/index.ts16
-rw-r--r--frontend/src/index.tsx21
-rw-r--r--frontend/src/plugin-loader.ts131
-rw-r--r--frontend/src/plugin-loader.tsx140
7 files changed, 253 insertions, 147 deletions
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 <iframe style={{ border: 'none', width: '100%', height: '100%' }} src={this.props.url}></iframe>
+ }
+}
+
+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<any> }> {
+ 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(<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;
+ }
+ }
+}
+
+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 <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>
+ }
+}
+
+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<string, Plugin> = {};
- 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: <TitleView />,
+ content: <PluginView />,
+ 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 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: <FaPlug />,
+ content: <LegacyPlugin url={ url } />
+ });
+ }
+
+ 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