feat: plugin install/uninstall lifecycle + UI buttons
- AppConfig: add InstalledPlugins []string - Manager.Discover(): no config dependency, all plugins start inactive - Manager.SyncConfig(): apply installed/enabled state from AppConfig - Manager.Enable(): works for plugins without on_install hook - Manager.Install/Uninstall(): run on_install/on_uninstall hooks - ActivatePlugin: skip if HasInstall && !Installed - ReloadPlugins: Discover → SyncConfig → InitRuntimes - Bindings: InstallPlugin, UninstallPlugin - SettingsPlugins: install/uninstall buttons, toggle only after install - Calendar: migration moved from on_init to on_install, on_uninstall drops tables - Tests: all 12 pass (manager + runtime + calendar)
This commit is contained in:
@@ -35,6 +35,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function install(p) {
|
||||
error = ''
|
||||
try {
|
||||
await wailsCall('InstallPlugin', p.name)
|
||||
p.installed = true
|
||||
// Refresh list to reflect new state
|
||||
plugins = await wailsCall('ListPlugins') || []
|
||||
} catch (e) {
|
||||
error = String(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function uninstall(p) {
|
||||
if (!confirm(`Удалить плагин «${p.name}»? Все данные плагина будут очищены. Файлы плагина сохранятся.`)) return
|
||||
error = ''
|
||||
try {
|
||||
await wailsCall('UninstallPlugin', p.name)
|
||||
p.installed = false
|
||||
p.active = false
|
||||
plugins = await wailsCall('ListPlugins') || []
|
||||
} catch (e) {
|
||||
error = String(e)
|
||||
}
|
||||
}
|
||||
|
||||
$: sidebarCount = plugins.reduce((n, p) => n + (p.uiContribs?.sidebarItems?.length || 0), 0)
|
||||
$: tabCount = plugins.reduce((n, p) => n + (p.uiContribs?.nodeTabs?.length || 0), 0)
|
||||
</script>
|
||||
@@ -82,15 +107,36 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="plugin-toggle">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
class:active={p.active}
|
||||
on:click={() => toggle(p)}
|
||||
role="switch"
|
||||
aria-checked={p.active}
|
||||
>
|
||||
<span class="toggle-knob"></span>
|
||||
</button>
|
||||
{#if p.hasInstall && !p.installed}
|
||||
<button class="install-btn" on:click={() => install(p)}>
|
||||
📦 Установить
|
||||
</button>
|
||||
{:else if p.hasInstall && p.installed}
|
||||
<div class="toggle-group">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
class:active={p.active}
|
||||
on:click={() => toggle(p)}
|
||||
role="switch"
|
||||
aria-checked={p.active}
|
||||
>
|
||||
<span class="toggle-knob"></span>
|
||||
</button>
|
||||
<button class="uninstall-btn btn-sm" on:click={() => uninstall(p)} title="Удалить плагин (очистить данные)">
|
||||
🗑
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class="toggle-btn"
|
||||
class:active={p.active}
|
||||
on:click={() => toggle(p)}
|
||||
role="switch"
|
||||
aria-checked={p.active}
|
||||
>
|
||||
<span class="toggle-knob"></span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
@@ -201,4 +247,42 @@
|
||||
.toggle-btn.active .toggle-knob {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.toggle-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.install-btn {
|
||||
padding: 6px 14px;
|
||||
background: #6366f1;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.install-btn:hover { filter: brightness(1.15); }
|
||||
|
||||
.uninstall-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: var(--surface, #1a1a2e);
|
||||
color: #ef4444;
|
||||
border: 1px solid rgba(239,68,68,0.3);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.6;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.uninstall-btn:hover {
|
||||
opacity: 1;
|
||||
background: rgba(239,68,68,0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user