webui: automation settings panel, set-location, responsive layout
- Automation tab: settings form (define_settings) beside the editor, stacking above it when narrow; script/settings save split. - Set location modal: current location + reset checkbox. - saveScriptSettings/setSavePath API wiring; plugins panel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5a5b5f4330
commit
9a1d67acd4
11 changed files with 741 additions and 13 deletions
99
public/js/plugins.js
Normal file
99
public/js/plugins.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// Frontend plugin registry.
|
||||
// Plugin modules call window.Naut.registerPlugin({...}) after they are imported.
|
||||
|
||||
const registry = {
|
||||
plugins: [],
|
||||
views: [],
|
||||
sidebarSections: [],
|
||||
detailTabs: [],
|
||||
detailPanels: [],
|
||||
};
|
||||
|
||||
let sharedContext = {};
|
||||
|
||||
function normalizeList(value) {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
function registerPlugin(plugin) {
|
||||
if (!plugin || !plugin.id || registry.plugins.some((p) => p.id === plugin.id)) return;
|
||||
registry.plugins.push(plugin);
|
||||
for (const view of normalizeList(plugin.views)) registry.views.push({ plugin, ...view });
|
||||
for (const section of normalizeList(plugin.sidebarSections)) registry.sidebarSections.push({ plugin, ...section });
|
||||
for (const tab of normalizeList(plugin.detailTabs)) registry.detailTabs.push({ plugin, ...tab });
|
||||
for (const panel of normalizeList(plugin.detailPanels)) registry.detailPanels.push({ plugin, ...panel });
|
||||
}
|
||||
|
||||
window.Naut = {
|
||||
...(window.Naut || {}),
|
||||
registerPlugin,
|
||||
plugins: registry,
|
||||
context: () => sharedContext,
|
||||
};
|
||||
|
||||
export async function loadPlugins(api, context) {
|
||||
sharedContext = context || {};
|
||||
let manifest;
|
||||
try { manifest = await api.plugins(); } catch { manifest = { modules: [] }; }
|
||||
for (const modulePath of manifest.modules || []) {
|
||||
try { await import(modulePath); }
|
||||
catch (e) { console.error(`Failed to load plugin ${modulePath}`, e); }
|
||||
}
|
||||
return registry.plugins;
|
||||
}
|
||||
|
||||
export function pluginViews() {
|
||||
return registry.views.filter((v) => v.id && v.label && typeof v.render === 'function');
|
||||
}
|
||||
|
||||
export function getPluginView(id) {
|
||||
return pluginViews().find((v) => v.id === id);
|
||||
}
|
||||
|
||||
export function renderPluginView(id, host, context) {
|
||||
const view = getPluginView(id);
|
||||
if (!view) return false;
|
||||
view.render(host, context);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function renderPluginSidebarSections(context) {
|
||||
return registry.sidebarSections
|
||||
.filter((s) => s.id && s.title && typeof s.render === 'function')
|
||||
.map((s) => `<div class="side-group plugin-side" data-plugin-sidebar="${s.plugin.id}:${s.id}">
|
||||
<div class="side-head">${context.f.esc(s.title)}</div>${s.render(context) || ''}
|
||||
</div>`)
|
||||
.join('');
|
||||
}
|
||||
|
||||
export function mountPluginSidebarSections(root, context) {
|
||||
for (const section of registry.sidebarSections) {
|
||||
const el = root.querySelector(`[data-plugin-sidebar="${CSS.escape(`${section.plugin.id}:${section.id}`)}"]`);
|
||||
if (el && typeof section.onMount === 'function') section.onMount(el, context);
|
||||
}
|
||||
}
|
||||
|
||||
export function pluginDetailTabs() {
|
||||
return registry.detailTabs.filter((t) => t.id && t.label && typeof t.render === 'function');
|
||||
}
|
||||
|
||||
export async function renderPluginDetailTab(id, host, context) {
|
||||
const tab = pluginDetailTabs().find((t) => t.id === id);
|
||||
if (!tab) return false;
|
||||
await tab.render(host, context);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function renderPluginDetailPanels(torrent, context) {
|
||||
return registry.detailPanels
|
||||
.filter((p) => p.title && typeof p.render === 'function')
|
||||
.map((p) => {
|
||||
try {
|
||||
return `<div class="section-h">${context.f.esc(p.title)}</div>${p.render(torrent, context) || ''}`;
|
||||
} catch (e) {
|
||||
console.error(`Failed to render plugin panel ${p.plugin.id}:${p.id || p.title}`, e);
|
||||
return '';
|
||||
}
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue