Add getRegisteredPlugins, plugin manager panel in test app
This commit is contained in:
@@ -105,7 +105,121 @@
|
||||
document.getElementById('status-badge').classList.add('loaded');
|
||||
addLog('example-plugin loaded successfully', 'success');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Plugin Manager panel
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Renders the plugin directory card list. */
|
||||
function refreshPluginList() {
|
||||
const listEl = document.getElementById('plugin-list');
|
||||
const plugins = registry.getRegisteredPlugins();
|
||||
|
||||
if (!plugins.length) {
|
||||
listEl.innerHTML = '<div id="no-plugins-msg" style="color:#555;font-size:12px;text-align:center;padding:20px 0;">No plugins loaded</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
listEl.innerHTML = plugins.map(p => `
|
||||
<div class="plugin-card" data-id="${p.id}">
|
||||
<div class="plugin-card-header">
|
||||
<span class="plugin-name">${p.name}</span>
|
||||
<span class="plugin-version">v${p.version}</span>
|
||||
</div>
|
||||
<div style="font-size:10px;color:#7986cb;font-family:monospace;">${p.id}</div>
|
||||
<div class="plugin-badges">
|
||||
<span class="badge green">loaded</span>
|
||||
${p.commandCount ? `<span class="badge">${p.commandCount} cmd${p.commandCount !== 1 ? 's' : ''}</span>` : ''}
|
||||
${p.interceptorCount ? `<span class="badge">${p.interceptorCount} interceptor${p.interceptorCount !== 1 ? 's' : ''}</span>` : ''}
|
||||
${p.themeCount ? `<span class="badge amber">${p.themeCount} theme${p.themeCount !== 1 ? 's' : ''}</span>` : ''}
|
||||
${p.hasSettings ? `<span class="badge">has settings</span>` : ''}
|
||||
</div>
|
||||
<button class="plugin-unload-btn" data-id="${p.id}">Unload</button>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
listEl.querySelectorAll('.plugin-unload-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const id = btn.dataset.id;
|
||||
await registry.unregisterPlugin(id);
|
||||
addLog(`Plugin unloaded: ${id}`, 'warn');
|
||||
refreshPluginList();
|
||||
refreshCommandList();
|
||||
renderSettings();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a plugin from an arbitrary file path entered in the Load input.
|
||||
* Supports CJS modules (same eval pattern as cinny-desktop).
|
||||
* @param {string} filePath
|
||||
*/
|
||||
async function loadPluginFromPath(filePath) {
|
||||
try {
|
||||
const code = fs.readFileSync(filePath, 'utf-8');
|
||||
const m = { exports: {} };
|
||||
// eslint-disable-next-line no-new-func
|
||||
new Function('module', 'exports', code)(m, m.exports);
|
||||
const p = m.exports;
|
||||
|
||||
if (!p || typeof p.onLoad !== 'function') {
|
||||
throw new Error('File does not export a valid plugin (needs an onLoad function)');
|
||||
}
|
||||
|
||||
const id = p.id ?? path.basename(path.dirname(filePath));
|
||||
if (registry.getRegisteredPlugins().some(r => r.id === id)) {
|
||||
throw new Error(`Plugin with id "${id}" is already loaded`);
|
||||
}
|
||||
|
||||
const ctx = createPluginContext(
|
||||
{
|
||||
pluginId: id,
|
||||
eventClient: eventEmitter,
|
||||
onNotify: (opts) =>
|
||||
addLog(`[${opts.type?.toUpperCase() ?? 'NOTIFY'}] ${opts.title}: ${opts.body}`, 'info'),
|
||||
},
|
||||
registry
|
||||
);
|
||||
ctx.log = (...args) => {
|
||||
console.log(`[Plugin ${id}]`, ...args);
|
||||
registry.addLog(id, 'log', args);
|
||||
addLog(args.map(String).join(' '), 'log');
|
||||
};
|
||||
|
||||
registry.registerPlugin(id, p, ctx);
|
||||
await p.onLoad(ctx);
|
||||
|
||||
addLog(`Plugin loaded from path: ${id}`, 'success');
|
||||
refreshPluginList();
|
||||
refreshCommandList();
|
||||
renderSettings();
|
||||
} catch (err) {
|
||||
addLog(`Failed to load plugin: ${err.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('load-plugin-btn').addEventListener('click', () => {
|
||||
const filePath = document.getElementById('load-plugin-path').value.trim();
|
||||
if (!filePath) { addLog('Enter a path to a plugin index.js', 'error'); return; }
|
||||
loadPluginFromPath(filePath);
|
||||
document.getElementById('load-plugin-path').value = '';
|
||||
});
|
||||
|
||||
document.getElementById('load-plugin-path').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') document.getElementById('load-plugin-btn').click();
|
||||
});
|
||||
|
||||
refreshPluginList();
|
||||
|
||||
// Populate registered command list
|
||||
function refreshCommandList() {
|
||||
const cmdListEl = document.getElementById('cmd-list');
|
||||
cmdListEl.innerHTML = registry
|
||||
.getCommands()
|
||||
.map(c => `<li><code>/${c.name}</code> — ${c.command.description ?? ''}</li>`)
|
||||
.join('') || '<li style="color:#555">No commands registered</li>';
|
||||
}
|
||||
|
||||
const cmdListEl = document.getElementById('cmd-list');
|
||||
cmdListEl.innerHTML = registry
|
||||
.getCommands()
|
||||
|
||||
Reference in New Issue
Block a user