summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorJonas Dellinger <jonas@dellinger.dev>2022-05-13 19:14:47 +0200
committerJonas Dellinger <jonas@dellinger.dev>2022-05-13 19:14:47 +0200
commit74438a31458af8bddd08d90eacc6d63677bab844 (patch)
treea7bfc044941f65c7f9971c5386c463eac31be768 /frontend/src
parent945db5de4788feefebc845817752472419051640 (diff)
downloaddecky-loader-74438a31458af8bddd08d90eacc6d63677bab844.tar.gz
decky-loader-74438a31458af8bddd08d90eacc6d63677bab844.zip
Work on react frontend loader
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/index.ts16
-rw-r--r--frontend/src/logger.ts35
-rw-r--r--frontend/src/plugin-loader.ts131
-rw-r--r--frontend/src/tabs-hook.ts69
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;