Work on react frontend loader

This commit is contained in:
Jonas Dellinger
2022-05-13 19:14:47 +02:00
parent 945db5de47
commit 74438a3145
25 changed files with 2921 additions and 608 deletions
+16
View File
@@ -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);
+35
View File
@@ -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;
+131
View File
@@ -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;
+69
View File
@@ -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;