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() {