fix(plugin-manager): sync UI state with plugin lifecycle + sidebar click fix
Root cause fixes: - Sidebar: handleSidebarItem used item.id instead of item.view for viewId. Platform Test sidebar item has id=verstak.platform-test.sidebar but view=verstak.platform-test.diagnostics. Click now dispatches correct viewId. - PluginManager: EnablePlugin/DisablePlugin only wrote to plugins.json but never re-discovered plugins. UI showed stale state (no Enable button after Disable, no Disable after Enable). Now calls ReloadPlugins() + loadAll() after each toggle. - PluginManager: loadAll() fired async loads (GetCapabilities etc) without awaiting — loading spinner disappeared before data was ready. Now awaits all via Promise.all. - PluginCard: no loading feedback on Enable/Disable buttons. Added actionFeedback prop — buttons show '⟳ Enabling...' / '⟳ Disabling...' and are disabled during operation. - PluginManager: no visible result after Reload/Enable/Disable. Added toast notifications (success/error/info) with auto-dismiss. - Settings: openSettingsFromProps didn't handle missing panel — now shows visible error in modal.
This commit is contained in:
parent
6d2f7858eb
commit
a100f5a441
|
|
@ -60,8 +60,12 @@
|
||||||
!capabilities.some(c => c.name === opt)
|
!capabilities.some(c => c.name === opt)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export let actionFeedback = {}; // { [pluginId]: 'enabling' | 'disabling' | null }
|
||||||
|
|
||||||
$: isDisabled = p.status === 'disabled' || !p.enabled;
|
$: isDisabled = p.status === 'disabled' || !p.enabled;
|
||||||
$: canToggle = p.status !== 'failed' && p.status !== 'incompatible' && p.status !== 'missing-required-capability' && p.status !== 'discovered';
|
$: canToggle = p.status !== 'failed' && p.status !== 'incompatible' && p.status !== 'missing-required-capability' && p.status !== 'discovered';
|
||||||
|
$: isBusy = actionFeedback[pluginId] != null;
|
||||||
|
$: busyAction = actionFeedback[pluginId] || null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="plugin-card" class:disabled={isDisabled} class:failed={p.status === 'failed'}>
|
<div class="plugin-card" class:disabled={isDisabled} class:failed={p.status === 'failed'}>
|
||||||
|
|
@ -181,12 +185,12 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if vaultOpen && canToggle}
|
{#if vaultOpen && canToggle}
|
||||||
{#if isDisabled}
|
{#if isDisabled}
|
||||||
<button class="btn-enable" on:click={() => onEnable(m.id)} type="button">
|
<button class="btn-enable" on:click={() => onEnable(m.id)} type="button" disabled={isBusy}>
|
||||||
▶ Enable
|
{#if busyAction === 'enabling'}⟳ Enabling...{:else}▶ Enable{/if}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="btn-disable" on:click={() => onDisable(m.id)} type="button">
|
<button class="btn-disable" on:click={() => onDisable(m.id)} type="button" disabled={isBusy}>
|
||||||
⏸ Disable
|
{#if busyAction === 'disabling'}⟳ Disabling...{:else}⏸ Disable{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@
|
||||||
let settingsPluginInfo = null;
|
let settingsPluginInfo = null;
|
||||||
let lastOpenedKey = '';
|
let lastOpenedKey = '';
|
||||||
|
|
||||||
|
// Per-action loading state — shows feedback on specific buttons without hiding the whole list
|
||||||
|
let actionFeedback = {}; // { [pluginId]: 'enabling' | 'disabling' | null }
|
||||||
|
let reloading = false;
|
||||||
|
let toastMessage = '';
|
||||||
|
let toastType = 'success'; // 'success' | 'error' | 'info'
|
||||||
|
|
||||||
export let activeSettingsPluginId = '';
|
export let activeSettingsPluginId = '';
|
||||||
export let activeSettingsPanelId = '';
|
export let activeSettingsPanelId = '';
|
||||||
|
|
||||||
|
|
@ -31,13 +37,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showToast(msg, type = 'success') {
|
||||||
|
toastMessage = msg;
|
||||||
|
toastType = type;
|
||||||
|
setTimeout(() => {
|
||||||
|
toastMessage = '';
|
||||||
|
}, 4000);
|
||||||
|
}
|
||||||
|
|
||||||
async function openSettingsFromProps(pluginId, panelId) {
|
async function openSettingsFromProps(pluginId, panelId) {
|
||||||
const panel = (contributions.settingsPanels || []).find(sp => sp.pluginId === pluginId && (!panelId || sp.id === panelId));
|
const panel = (contributions.settingsPanels || []).find(sp => sp.pluginId === pluginId && (!panelId || sp.id === panelId));
|
||||||
if (panel) {
|
if (panel) {
|
||||||
settingsPanel = panel;
|
settingsPanel = panel;
|
||||||
settingsPluginId = pluginId;
|
settingsPluginId = pluginId;
|
||||||
settingsError = null;
|
settingsError = null;
|
||||||
// Get plugin frontend info
|
|
||||||
try {
|
try {
|
||||||
const info = await GetPluginFrontendInfo(pluginId);
|
const info = await GetPluginFrontendInfo(pluginId);
|
||||||
settingsPluginInfo = info;
|
settingsPluginInfo = info;
|
||||||
|
|
@ -45,6 +58,8 @@
|
||||||
ReadPluginSettings(pluginId).then(data => {
|
ReadPluginSettings(pluginId).then(data => {
|
||||||
settingsData = data || {};
|
settingsData = data || {};
|
||||||
}).catch(() => { settingsData = {}; });
|
}).catch(() => { settingsData = {}; });
|
||||||
|
} else {
|
||||||
|
settingsError = `Settings panel not found for plugin "${pluginId}". Check that the plugin is enabled and has settingsPanels in its manifest.`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,50 +83,79 @@
|
||||||
loading = false;
|
loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Vault status — non-critical
|
// Collect all async loads but await them so loading stays true until all are done
|
||||||
GetVaultStatus().then(v => { vaultStatus = v || { status: 'unknown', path: '', vaultId: '' }; }).catch(() => {});
|
try {
|
||||||
// Vault plugin state
|
const [v, caps, perms, contribs] = await Promise.all([
|
||||||
if (vaultStatus.status === 'open') {
|
GetVaultStatus().catch(() => ({ status: 'unknown', path: '', vaultId: '' })),
|
||||||
GetVaultPluginState().then(s => { vaultPluginState = s || { enabledPlugins: [], disabledPlugins: [], desiredPlugins: [] }; }).catch(() => {});
|
GetCapabilities().catch(() => []),
|
||||||
|
GetPermissions().catch(() => []),
|
||||||
|
GetContributions().catch(() => ({})),
|
||||||
|
]);
|
||||||
|
vaultStatus = v || { status: 'unknown', path: '', vaultId: '' };
|
||||||
|
capabilities = caps || [];
|
||||||
|
permissions = perms || [];
|
||||||
|
contributions = contribs || {};
|
||||||
|
} catch (e) {
|
||||||
|
// Non-critical — log but don't fail
|
||||||
|
console.error('[PluginManager] non-critical load error:', e);
|
||||||
|
}
|
||||||
|
if (vaultStatus.status === 'open') {
|
||||||
|
try {
|
||||||
|
vaultPluginState = await GetVaultPluginState() || { enabledPlugins: [], disabledPlugins: [], desiredPlugins: [] };
|
||||||
|
} catch { /* non-critical */ }
|
||||||
}
|
}
|
||||||
// Capabilities and permissions are non-critical — load async
|
|
||||||
GetCapabilities().then(c => { capabilities = c || []; }).catch(() => {});
|
|
||||||
GetPermissions().then(p => { permissions = p || []; }).catch(() => {});
|
|
||||||
GetContributions().then(c => { contributions = c || {}; }).catch(() => {});
|
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => { loadAll(); });
|
onMount(() => { loadAll(); });
|
||||||
|
|
||||||
async function reload() {
|
async function reload() {
|
||||||
loading = true;
|
reloading = true;
|
||||||
error = '';
|
error = '';
|
||||||
|
let resultMsg = '';
|
||||||
try {
|
try {
|
||||||
await ReloadPlugins();
|
const [count, summary] = await ReloadPlugins();
|
||||||
|
resultMsg = `Reloaded ${count} plugin(s). ${summary}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = 'Reload: ' + String(e);
|
error = 'Reload: ' + String(e);
|
||||||
loading = false;
|
reloading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await loadAll();
|
await loadAll();
|
||||||
|
reloading = false;
|
||||||
|
showToast(resultMsg, 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function enablePlugin(pluginId) {
|
async function enablePlugin(pluginId) {
|
||||||
|
actionFeedback = { ...actionFeedback, [pluginId]: 'enabling' };
|
||||||
|
error = '';
|
||||||
const err = await EnablePlugin(pluginId);
|
const err = await EnablePlugin(pluginId);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
actionFeedback = { ...actionFeedback, [pluginId]: null };
|
||||||
error = 'Enable: ' + err;
|
error = 'Enable: ' + err;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await reload();
|
// Reload to get updated state
|
||||||
|
try { await ReloadPlugins(); } catch (e) { /* ignore */ }
|
||||||
|
await loadAll();
|
||||||
|
actionFeedback = { ...actionFeedback, [pluginId]: null };
|
||||||
|
showToast(`Plugin "${pluginId}" enabled`, 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function disablePlugin(pluginId) {
|
async function disablePlugin(pluginId) {
|
||||||
|
actionFeedback = { ...actionFeedback, [pluginId]: 'disabling' };
|
||||||
|
error = '';
|
||||||
const err = await DisablePlugin(pluginId);
|
const err = await DisablePlugin(pluginId);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
actionFeedback = { ...actionFeedback, [pluginId]: null };
|
||||||
error = 'Disable: ' + err;
|
error = 'Disable: ' + err;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await reload();
|
// Reload to get updated state
|
||||||
|
try { await ReloadPlugins(); } catch (e) { /* ignore */ }
|
||||||
|
await loadAll();
|
||||||
|
actionFeedback = { ...actionFeedback, [pluginId]: null };
|
||||||
|
showToast(`Plugin "${pluginId}" disabled`, 'info');
|
||||||
}
|
}
|
||||||
|
|
||||||
$: totalPlugins = plugins.length;
|
$: totalPlugins = plugins.length;
|
||||||
|
|
@ -134,6 +178,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="plugin-manager">
|
<div class="plugin-manager">
|
||||||
|
<!-- Toast notification -->
|
||||||
|
{#if toastMessage}
|
||||||
|
<div class="toast" class:toast-success={toastType === 'success'} class:toast-error={toastType === 'error'} class:toast-info={toastType === 'info'}>
|
||||||
|
{toastMessage}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<h2>Plugin Manager</h2>
|
<h2>Plugin Manager</h2>
|
||||||
|
|
@ -143,8 +194,8 @@
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<button class="reload-btn" on:click={reload} type="button" disabled={loading}>
|
<button class="reload-btn" on:click={reload} type="button" disabled={loading || reloading}>
|
||||||
{loading ? '⟳ Loading...' : '⟳ Reload'}
|
{reloading ? '⟳ Reloading...' : '⟳ Reload'}
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
@ -181,7 +232,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<div class="plugin-list">
|
<div class="plugin-list">
|
||||||
{#each plugins as p}
|
{#each plugins as p}
|
||||||
<PluginCard {p} {capabilities} {permissions} {contributions} {vaultOpen} settingsPanels={(contributions.settingsPanels || []).filter(sp => sp.pluginId === p.manifest?.id)} onEnable={enablePlugin} onDisable={disablePlugin} />
|
<PluginCard {p} {capabilities} {permissions} {contributions} {vaultOpen} {actionFeedback} settingsPanels={(contributions.settingsPanels || []).filter(sp => sp.pluginId === p.manifest?.id)} onEnable={enablePlugin} onDisable={disablePlugin} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -278,6 +329,7 @@
|
||||||
.plugin-manager {
|
.plugin-manager {
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -300,32 +352,29 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
}
|
}
|
||||||
.vault-open {
|
.vault-open { background: rgba(78, 204, 163, 0.15); color: #4ecca3; border-color: #4ecca3; }
|
||||||
background: rgba(78, 204, 163, 0.15);
|
.vault-not-created { background: rgba(255, 200, 87, 0.15); color: #ffc857; border-color: #ffc857; }
|
||||||
color: #4ecca3;
|
.vault-closed { background: rgba(160, 160, 184, 0.15); color: #a0a0b8; border-color: #a0a0b8; }
|
||||||
border-color: #4ecca3;
|
.vault-error { background: rgba(233, 69, 96, 0.15); color: #e94560; border-color: #e94560; }
|
||||||
}
|
|
||||||
.vault-not-created {
|
|
||||||
background: rgba(255, 200, 87, 0.15);
|
|
||||||
color: #ffc857;
|
|
||||||
border-color: #ffc857;
|
|
||||||
}
|
|
||||||
.vault-closed {
|
|
||||||
background: rgba(160, 160, 184, 0.15);
|
|
||||||
color: #a0a0b8;
|
|
||||||
border-color: #a0a0b8;
|
|
||||||
}
|
|
||||||
.vault-error {
|
|
||||||
background: rgba(233, 69, 96, 0.15);
|
|
||||||
color: #e94560;
|
|
||||||
border-color: #e94560;
|
|
||||||
}
|
|
||||||
.reload-btn {
|
.reload-btn {
|
||||||
background: #0f3460; color: #e0e0e0; border: 1px solid #533483;
|
background: #0f3460; color: #e0e0e0; border: 1px solid #533483;
|
||||||
padding: 0.4rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.85rem;
|
padding: 0.4rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.reload-btn:hover:not(:disabled) { background: #533483; }
|
.reload-btn:hover:not(:disabled) { background: #533483; }
|
||||||
.reload-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
.reload-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
|
||||||
|
/* Toast */
|
||||||
|
.toast {
|
||||||
|
position: fixed; top: 1rem; right: 1rem; z-index: 2000;
|
||||||
|
padding: 0.6rem 1.2rem; border-radius: 6px; font-size: 0.85rem;
|
||||||
|
max-width: 400px; word-break: break-word;
|
||||||
|
animation: toastIn 0.25s ease-out;
|
||||||
|
}
|
||||||
|
.toast-success { background: #1a3a2e; color: #4ecca3; border: 1px solid #4ecca3; }
|
||||||
|
.toast-error { background: #3a1a1a; color: #e94560; border: 1px solid #e94560; }
|
||||||
|
.toast-info { background: #1a1a3a; color: #a78bfa; border: 1px solid #a78bfa; }
|
||||||
|
@keyframes toastIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
|
|
||||||
.loading, .error {
|
.loading, .error {
|
||||||
padding: 2rem; text-align: center; color: #a0a0b8;
|
padding: 2rem; text-align: center; color: #a0a0b8;
|
||||||
}
|
}
|
||||||
|
|
@ -357,70 +406,30 @@
|
||||||
.hint code { background: #0f3460; padding: 0.1rem 0.3rem; border-radius: 3px; }
|
.hint code { background: #0f3460; padding: 0.1rem 0.3rem; border-radius: 3px; }
|
||||||
.plugin-list { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.5rem; }
|
.plugin-list { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.5rem; }
|
||||||
|
|
||||||
/* Missing installed section */
|
.missing-section { margin-bottom: 1.5rem; }
|
||||||
.missing-section {
|
.missing-section h3 { color: #e94560; font-size: 1rem; margin: 0 0 0.25rem; }
|
||||||
margin-bottom: 1.5rem;
|
.missing-hint { color: #a0a0b8; font-size: 0.8rem; margin: 0 0 0.75rem; }
|
||||||
}
|
.missing-card { border-color: #e94560; opacity: 0.8; }
|
||||||
.missing-section h3 {
|
.missing-text { color: #a0a0b8; font-size: 0.85rem; margin: 0.5rem 0 0; }
|
||||||
color: #e94560;
|
.source-hint { display: block; margin-top: 0.25rem; font-size: 0.75rem; color: #666; }
|
||||||
font-size: 1rem;
|
|
||||||
margin: 0 0 0.25rem;
|
|
||||||
}
|
|
||||||
.missing-hint {
|
|
||||||
color: #a0a0b8;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin: 0 0 0.75rem;
|
|
||||||
}
|
|
||||||
.missing-card {
|
|
||||||
border-color: #e94560;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
.missing-text {
|
|
||||||
color: #a0a0b8;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin: 0.5rem 0 0;
|
|
||||||
}
|
|
||||||
.source-hint {
|
|
||||||
display: block;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.registry-section {
|
.registry-section {
|
||||||
background: #16213e; border: 1px solid #0f3460;
|
background: #16213e; border: 1px solid #0f3460;
|
||||||
border-radius: 8px; padding: 0.75rem; margin-top: 1rem;
|
border-radius: 8px; padding: 0.75rem; margin-top: 1rem;
|
||||||
}
|
}
|
||||||
.registry-section summary {
|
.registry-section summary { cursor: pointer; color: #a0a0b8; font-size: 0.9rem; font-weight: 600; }
|
||||||
cursor: pointer; color: #a0a0b8; font-size: 0.9rem; font-weight: 600;
|
|
||||||
}
|
|
||||||
table { width: 100%; margin-top: 0.5rem; border-collapse: collapse; font-size: 0.85rem; }
|
table { width: 100%; margin-top: 0.5rem; border-collapse: collapse; font-size: 0.85rem; }
|
||||||
th {
|
th { text-align: left; padding: 0.4rem 0.5rem; color: #a0a0b8; border-bottom: 1px solid #0f3460; }
|
||||||
text-align: left; padding: 0.4rem 0.5rem; color: #a0a0b8; border-bottom: 1px solid #0f3460;
|
|
||||||
}
|
|
||||||
td { padding: 0.3rem 0.5rem; border-bottom: 1px solid #0f3460; }
|
td { padding: 0.3rem 0.5rem; border-bottom: 1px solid #0f3460; }
|
||||||
td code { color: #e0e0e0; }
|
td code { color: #e0e0e0; }
|
||||||
:global(.status-stable) { color: #4ecca3; }
|
:global(.status-stable) { color: #4ecca3; }
|
||||||
:global(.status-draft) { color: #ffc857; }
|
:global(.status-draft) { color: #ffc857; }
|
||||||
:global(.status-deprecated) { color: #e94560; }
|
:global(.status-deprecated) { color: #e94560; }
|
||||||
.source-badge {
|
.source-badge { font-size: 0.75rem; padding: 0.1rem 0.4rem; border-radius: 4px; font-weight: 600; }
|
||||||
font-size: 0.75rem;
|
.source-core { background: #1a3a5c; color: #4ecca3; border: 1px solid #4ecca3; }
|
||||||
padding: 0.1rem 0.4rem;
|
.source-plugin { background: #0f3460; color: #a0a0b8; border: 1px solid #533483; }
|
||||||
border-radius: 4px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.source-core {
|
|
||||||
background: #1a3a5c;
|
|
||||||
color: #4ecca3;
|
|
||||||
border: 1px solid #4ecca3;
|
|
||||||
}
|
|
||||||
.source-plugin {
|
|
||||||
background: #0f3460;
|
|
||||||
color: #a0a0b8;
|
|
||||||
border: 1px solid #533483;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Modal ── */
|
/* Modal */
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
position: fixed; inset: 0;
|
position: fixed; inset: 0;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
|
@ -428,77 +437,17 @@
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
.modal {
|
.modal {
|
||||||
background: #16213e;
|
background: #16213e; border: 1px solid #0f3460; border-radius: 8px;
|
||||||
border: 1px solid #0f3460;
|
width: 480px; max-width: 90vw; max-height: 80vh; display: flex; flex-direction: column;
|
||||||
border-radius: 8px;
|
|
||||||
width: 480px;
|
|
||||||
max-width: 90vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
.modal-header {
|
.modal-header {
|
||||||
display: flex;
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
align-items: center;
|
padding: 1rem; border-bottom: 1px solid #0f3460;
|
||||||
justify-content: space-between;
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid #0f3460;
|
|
||||||
}
|
}
|
||||||
.modal-header h3 { margin: 0; color: #e0e0f0; font-size: 1.1rem; }
|
.modal-header h3 { margin: 0; color: #e0e0f0; font-size: 1.1rem; }
|
||||||
.modal-close {
|
.modal-close { background: none; border: none; color: #a0a0b8; font-size: 1.2rem; cursor: pointer; padding: 0.2rem 0.5rem; }
|
||||||
background: none; border: none; color: #a0a0b8;
|
|
||||||
font-size: 1.2rem; cursor: pointer; padding: 0.2rem 0.5rem;
|
|
||||||
}
|
|
||||||
.modal-close:hover { color: #e94560; }
|
.modal-close:hover { color: #e94560; }
|
||||||
.modal-body { padding: 1rem; overflow-y: auto; }
|
.modal-body { padding: 1rem; overflow-y: auto; }
|
||||||
.settings-hint { color: #666; font-size: 0.8rem; margin: 0.25rem 0; }
|
.settings-hint { color: #666; font-size: 0.8rem; margin: 0.25rem 0; }
|
||||||
.settings-hint code { color: #4ecca3; }
|
.settings-hint code { color: #4ecca3; }
|
||||||
|
|
||||||
/* ── Settings Form ── */
|
|
||||||
.settings-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
.settings-form h4 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
color: #e0e0f0;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
.form-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
.form-row label {
|
|
||||||
color: #a0a0b8;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.form-row input[type="text"],
|
|
||||||
.form-row input[type="number"] {
|
|
||||||
background: #0f3460;
|
|
||||||
border: 1px solid #1a3a5c;
|
|
||||||
color: #e0e0f0;
|
|
||||||
padding: 0.4rem 0.6rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.form-row input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4ecca3;
|
|
||||||
}
|
|
||||||
.btn-save {
|
|
||||||
background: #4ecca3;
|
|
||||||
color: #1a1a2e;
|
|
||||||
border: none;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
.btn-save:hover {
|
|
||||||
background: #3dbb92;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSidebarItem(item) {
|
function handleSidebarItem(item) {
|
||||||
window.dispatchEvent(new CustomEvent('verstak:open-view', { detail: { viewId: item.id, pluginId: item.pluginId } }));
|
// Use item.view (the view contribution ID) if available, fall back to item.id
|
||||||
|
const viewId = item.view || item.id;
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:open-view', { detail: { viewId, pluginId: item.pluginId } }));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue