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:
ookami125 2026-06-21 23:22:00 -04:00
parent 5a5b5f4330
commit 9a1d67acd4
11 changed files with 741 additions and 13 deletions

View file

@ -2,8 +2,9 @@
import { api } from './api.js';
import * as f from './format.js';
import { pluginDetailTabs, renderPluginDetailPanels, renderPluginDetailTab } from './plugins.js';
const TABS = ['general', 'trackers', 'peers', 'content', 'pieces'];
const BUILTIN_TABS = ['general', 'trackers', 'peers', 'content', 'pieces'];
let activeTab = 'general';
let currentHash = null;
let refreshTimer = null;
@ -22,7 +23,7 @@ export function renderDetailShell(hash, host) {
<div class="detail" id="detailPanel">
<div class="detail-resize" id="detailResize" title="Drag to resize"></div>
<div class="detail-tabs">
${TABS.map((t) => `<button class="dtab ${t === activeTab ? 'active' : ''}" data-dtab="${t}">${tabLabel(t)}</button>`).join('')}
${detailTabs().map((t) => `<button class="dtab ${t.id === activeTab ? 'active' : ''}" data-dtab="${t.id}">${t.label}</button>`).join('')}
<button class="dtab detail-close" data-act="closeDetail" title="Close detail (Esc)"></button>
</div>
<div class="detail-body" id="detailBody"></div>
@ -39,6 +40,13 @@ function tabLabel(t) {
return { general: 'General', trackers: 'Trackers', peers: 'Peers', content: 'Content', pieces: 'Pieces' }[t];
}
function detailTabs() {
return [
...BUILTIN_TABS.map((id) => ({ id, label: tabLabel(id) })),
...pluginDetailTabs().map((tab) => ({ id: tab.id, label: tab.label })),
];
}
export function closeDetail() {
currentHash = null;
if (refreshTimer) clearInterval(refreshTimer);
@ -58,6 +66,7 @@ async function loadTab() {
else if (activeTab === 'peers') body.innerHTML = renderPeers(await api.peers(currentHash));
else if (activeTab === 'content') { body.innerHTML = renderFiles(await api.files(currentHash)); bindFilePriorities(body); }
else if (activeTab === 'pieces') body.innerHTML = renderPieces(await api.pieces(currentHash));
else await renderPluginDetailTab(activeTab, body, { api, f, hash: currentHash });
} catch { /* ignore transient */ }
};
await render();
@ -107,6 +116,7 @@ function renderGeneral(t) {
${row('Auto TMM', t.autoTMM ? 'On' : 'Off')}
${row('Force start', t.forceStart ? 'On' : 'Off')}
${t.comment ? row('Comment', f.esc(t.comment)) : ''}
${renderPluginDetailPanels(t, { api, f, hash: t.hash })}
</div>`;
}