feat: simplify install button + add tab state and management methods to store

Replace 3-option dropdown with single button opening tabbed main.html. Add activeTab, fetchInstalledPluginInfo, and management action methods that delegate to pluginListStore for installed plugin operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

TerminallyLazy committed Mar 3, 2026 at 23:39 UTC 1e536e9f87764a372a6958828151c4e21f77f560
2 files changed +83 -131
plugins/plugin_installer/extensions/webui/plugins-list-header-buttons/install-buttons.html
+6 -131
@@ -1,136 +1,11 @@
1 -<span x-data="{ open: false }">
1 +<span x-data>
2 <script type="module">
3 import { store } from "/plugins/plugin_installer/webui/pluginInstallStore.js";
4 </script>
5
6 - <div class="pi-install-wrapper" @click.outside="open = false">
7 - <button type="button"
8 - class="button"
9 - @click="open = !open">
10 - <span class="icon material-symbols-outlined">add</span> Install
11 - <span class="material-symbols-outlined pi-chevron"
12 - :class="{ 'pi-chevron-open': open }">expand_more</span>
13 - </button>
14 -
15 - <div class="pi-dropdown" x-show="open" x-cloak x-transition.opacity.duration.150ms>
16 - <button type="button" class="pi-dropdown-item"
17 - @click="open = false; openModal('../plugins/plugin_installer/webui/install-index.html')">
18 - <span class="material-symbols-outlined">store</span>
19 - <span class="pi-item-text">
20 - <span class="pi-item-title">Browse Plugins</span>
21 - <span class="pi-item-desc">Explore the community index</span>
22 - </span>
23 - </button>
24 - <div class="pi-dropdown-sep"></div>
25 - <button type="button" class="pi-dropdown-item"
26 - @click="open = false; openModal('../plugins/plugin_installer/webui/install-git.html')">
27 - <span class="material-symbols-outlined">terminal</span>
28 - <span class="pi-item-text">
29 - <span class="pi-item-title">Clone from Git</span>
30 - <span class="pi-item-desc">Install from a repository URL</span>
31 - </span>
32 - </button>
33 - <button type="button" class="pi-dropdown-item"
34 - @click="open = false; openModal('../plugins/plugin_installer/webui/install-zip.html')">
35 - <span class="material-symbols-outlined">upload_file</span>
36 - <span class="pi-item-text">
37 - <span class="pi-item-title">Upload ZIP</span>
38 - <span class="pi-item-desc">Install from a local archive</span>
39 - </span>
40 - </button>
41 - </div>
42 - </div>
43 -
44 - <style>
45 - .plugins-toolbar-actions {
46 - display: flex;
47 - align-items: center;
48 - gap: 0.4rem;
49 - }
50 -
51 - .pi-install-wrapper {
52 - position: relative;
53 - }
54 -
55 - .pi-install-wrapper > .button {
56 - gap: 0.25rem;
57 - }
58 -
59 - .pi-chevron {
60 - font-size: 1.1rem !important;
61 - transition: transform 0.2s ease;
62 - }
63 -
64 - .pi-chevron-open {
65 - transform: rotate(180deg);
66 - }
67 -
68 - .pi-dropdown {
69 - position: absolute;
70 - top: calc(100% + 0.35rem);
71 - right: 0;
72 - min-width: 230px;
73 - background: var(--color-panel);
74 - border: 1px solid var(--color-border);
75 - border-radius: 8px;
76 - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
77 - z-index: 100;
78 - padding: 0.3rem;
79 - }
80 -
81 - .pi-dropdown-item {
82 - display: flex;
83 - align-items: center;
84 - gap: 0.6rem;
85 - width: 100%;
86 - padding: 0.5rem 0.6rem;
87 - background: none;
88 - border: none;
89 - border-radius: 5px;
90 - color: var(--color-text);
91 - cursor: pointer;
92 - text-align: left;
93 - font-family: inherit;
94 - font-size: 0.875rem;
95 - transition: background 0.12s ease;
96 - }
97 -
98 - .pi-dropdown-item:hover {
99 - background: var(--color-background);
100 - }
101 -
102 - .pi-dropdown-item .material-symbols-outlined {
103 - font-size: 1.25rem;
104 - color: var(--color-text-secondary);
105 - flex-shrink: 0;
106 - }
107 -
108 - .pi-dropdown-item:hover .material-symbols-outlined {
109 - color: var(--color-highlight);
110 - }
111 -
112 - .pi-item-text {
113 - display: flex;
114 - flex-direction: column;
115 - }
116 -
117 - .pi-item-title {
118 - font-weight: 500;
119 - line-height: 1.3;
120 - }
121 -
122 - .pi-item-desc {
123 - font-size: 0.75rem;
124 - color: var(--color-text-secondary);
125 - line-height: 1.3;
126 - }
127 -
128 - .pi-dropdown-sep {
129 - height: 1px;
130 - background: var(--color-border);
131 - margin: 0.2rem 0.5rem;
132 - }
133 -
134 - [x-cloak] { display: none !important; }
135 - </style>
6 + <button type="button"
7 + class="button"
8 + @click="openModal('../plugins/plugin_installer/webui/main.html')">
9 + <span class="icon material-symbols-outlined">add</span> Install
10 + </button>
11 </span>
plugins/plugin_installer/webui/pluginInstallStore.js
+77
@@ -44,6 +44,15 @@ const model = {
44 error: "",
45 result: null,
46
47 + // Tab state
48 + activeTab: "store",
49 +
50 + setTab(tab) {
51 + this.activeTab = tab;
52 + this.error = "";
53 + this.result = null;
54 + },
55 +
56 // ── ZIP Install ──────────────────────────────
57
58 handleFileUpload(event) {
@@ -223,6 +232,10 @@ const model = {
232 this.selectedPlugin = plugin;
233 this.error = "";
234 this.result = null;
235 + this.installedPluginInfo = null;
236 + if (plugin.installed) {
237 + this.fetchInstalledPluginInfo(plugin.key);
238 + }
239 window.openModal?.("../plugins/plugin_installer/webui/install-detail.html");
240 },
241
@@ -272,6 +285,70 @@ const model = {
285 }
286 },
287
288 + // ── Installed Plugin Info ─────────────────────
289 +
290 + installedPluginInfo: null,
291 + installedPluginInfoLoading: false,
292 +
293 + async fetchInstalledPluginInfo(pluginKey) {
294 + this.installedPluginInfo = null;
295 + this.installedPluginInfoLoading = true;
296 + try {
297 + const response = await api.callJsonApi("plugins_list", {
298 + filter: { custom: true, builtin: true, search: "" },
299 + });
300 + const plugins = Array.isArray(response.plugins) ? response.plugins : [];
301 + this.installedPluginInfo = plugins.find(
302 + (p) => p.name === pluginKey
303 + ) || null;
304 + } catch (e) {
305 + this.installedPluginInfo = null;
306 + } finally {
307 + this.installedPluginInfoLoading = false;
308 + }
309 + },
310 +
311 + manageOpenPlugin() {
312 + const info = this.installedPluginInfo;
313 + if (!info?.name || !info?.has_main_screen) return;
314 + window.openModal?.(`/plugins/${info.name}/webui/main.html`);
315 + },
316 +
317 + async manageOpenConfig() {
318 + const pls = Alpine.store("pluginListStore");
319 + if (pls?.openPluginConfig && this.installedPluginInfo) {
320 + await pls.openPluginConfig(this.installedPluginInfo);
321 + }
322 + },
323 +
324 + async manageOpenDoc(doc) {
325 + const pls = Alpine.store("pluginListStore");
326 + if (pls?.openPluginDoc && this.installedPluginInfo) {
327 + await pls.openPluginDoc(this.installedPluginInfo, doc);
328 + }
329 + },
330 +
331 + manageOpenInfo() {
332 + const pls = Alpine.store("pluginListStore");
333 + if (pls?.openPluginInfo && this.installedPluginInfo) {
334 + pls.openPluginInfo(this.installedPluginInfo);
335 + }
336 + },
337 +
338 + async manageDeletePlugin() {
339 + const pls = Alpine.store("pluginListStore");
340 + if (pls?.deletePlugin && this.installedPluginInfo) {
341 + await pls.deletePlugin(this.installedPluginInfo);
342 + // Mark as no longer installed in the index view
343 + if (this.selectedPlugin) {
344 + this.selectedPlugin.installed = false;
345 + const idx = this.installedPlugins.indexOf(this.selectedPlugin.key);
346 + if (idx !== -1) this.installedPlugins.splice(idx, 1);
347 + }
348 + this.installedPluginInfo = null;
349 + }
350 + },
351 +
352 // ── Shared ───────────────────────────────────
353
354 resetZip() {