diff options
| author | Jonas Dellinger <jonas@dellinger.dev> | 2022-05-13 19:14:47 +0200 |
|---|---|---|
| committer | Jonas Dellinger <jonas@dellinger.dev> | 2022-05-13 19:14:47 +0200 |
| commit | 74438a31458af8bddd08d90eacc6d63677bab844 (patch) | |
| tree | a7bfc044941f65c7f9971c5386c463eac31be768 /frontend/src | |
| parent | 945db5de4788feefebc845817752472419051640 (diff) | |
| download | decky-loader-74438a31458af8bddd08d90eacc6d63677bab844.tar.gz decky-loader-74438a31458af8bddd08d90eacc6d63677bab844.zip | |
Work on react frontend loader
Diffstat (limited to 'frontend/src')
| -rw-r--r-- | frontend/src/index.ts | 16 | ||||
| -rw-r--r-- | frontend/src/logger.ts | 35 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.ts | 131 | ||||
| -rw-r--r-- | frontend/src/tabs-hook.ts | 69 |
4 files changed, 251 insertions, 0 deletions
diff --git a/frontend/src/index.ts b/frontend/src/index.ts new file mode 100644 index 00000000..390b83c9 --- /dev/null +++ b/frontend/src/index.ts @@ -0,0 +1,16 @@ +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/logger.ts b/frontend/src/logger.ts new file mode 100644 index 00000000..9eb515a3 --- /dev/null +++ b/frontend/src/logger.ts @@ -0,0 +1,35 @@ +export const log = (name: string, ...args: any[]) => { + console.log( + `%c Decky %c ${name} %c`, + 'background: #16a085; color: black;', + 'background: #1abc9c; color: black;', + 'background: transparent;', + ...args, + ); +}; + +export const error = (name: string, ...args: any[]) => { + console.log( + `%c Decky %c ${name} %c`, + 'background: #16a085; color: black;', + 'background: #FF0000;', + 'background: transparent;', + ...args, + ); +}; + +class Logger { + constructor(private name: string) { + this.name = name; + } + + log(...args: any[]) { + log(this.name, ...args); + } + + debug(...args: any[]) { + log(this.name, ...args); + } +} + +export default Logger; diff --git a/frontend/src/plugin-loader.ts b/frontend/src/plugin-loader.ts new file mode 100644 index 00000000..9a72ea84 --- /dev/null +++ b/frontend/src/plugin-loader.ts @@ -0,0 +1,131 @@ +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/tabs-hook.ts b/frontend/src/tabs-hook.ts new file mode 100644 index 00000000..17f41d91 --- /dev/null +++ b/frontend/src/tabs-hook.ts @@ -0,0 +1,69 @@ +import Logger from './logger'; + +declare global { + interface Window { + __TABS_HOOK_INSTANCE: any; + } + interface Array<T> { + __filter: any; + } +} + +const isTabsArray = (tabs) => { + const length = tabs.length; + return length === 7 && tabs[length - 1]?.key === 6 && tabs[length - 1]?.tab; +}; + +interface Tab { + id: string; + title: any; + content: any; + icon: any; +} + +class TabsHook extends Logger { + // private keys = 7; + tabs: Tab[] = []; + + constructor() { + super('TabsHook'); + + this.log('Initialized'); + window.__TABS_HOOK_INSTANCE = this; + + const self = this; + + const filter = Array.prototype.__filter ?? Array.prototype.filter; + Array.prototype.__filter = filter; + Array.prototype.filter = function (...args) { + if (isTabsArray(this)) { + self.render(this); + } + // @ts-ignore + return filter.call(this, ...args); + }; + } + + add(tab: Tab) { + this.log('Adding tab', tab.id, 'to render array'); + this.tabs.push(tab); + } + + removeById(id: string) { + this.log('Removing tab', id); + this.tabs = this.tabs.filter((tab) => tab.id !== id); + } + + render(existingTabs: any[]) { + for (const { title, icon, content, id } of this.tabs) { + existingTabs.push({ + key: id, + title, + tab: icon, + panel: content, + }); + } + } +} + +export default TabsHook; |
