mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 16:41:57 +00:00
Work on react frontend loader
This commit is contained in:
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user