feat(ui): add enable/disable toggle to the plugin-list tab

Each expanded plugin card gains an enable/disable button wired to
pluginInventory.setEnabled; the tab re-lists after toggling. Adds zh/en
copy for the toggle states.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-14 13:53:36 +08:00
parent 3e0eca752c
commit f01795ea97
4 changed files with 42 additions and 3 deletions
@@ -277,3 +277,8 @@
grid-template-columns: minmax(0, 1fr);
}
}
.toggle {
margin-top: 0.5rem;
align-self: flex-start;
}
@@ -12,6 +12,8 @@ import css from './PluginInventorySettingsTab.module.css'
export interface PluginInventorySettingsTabInjected {
/** Read a current Host inventory snapshot. */
list: () => Promise<PluginInventorySnapshot>
/** Toggle one plugin entry on or off; persists across a restart. */
setEnabled: (entryId: PluginInventoryEntry['entryId'], enabled: boolean) => Promise<void>
}
type PluginInventoryEntry = PluginInventorySnapshot['entries'][number]
@@ -60,13 +62,14 @@ function matches(entry: PluginInventoryEntry, normalizedQuery: string): boolean
.some(value => value.toLocaleLowerCase().includes(normalizedQuery))
}
/** Render the read-only current Loader inventory. */
export function PluginInventorySettingsTab({ list, t }: PluginInventorySettingsTabProps): ReactNode {
/** Render the current Loader inventory with per-plugin enable/disable. */
export function PluginInventorySettingsTab({ list, setEnabled, t }: PluginInventorySettingsTabProps): ReactNode {
const catalogId = useId()
const [request, setRequest] = useState(0)
const [query, setQuery] = useState('')
const [expanded, setExpanded] = useState<PluginInventoryEntry['entryId'] | null>(null)
const [state, setState] = useState<ViewState>({ status: 'loading' })
const [busy, setBusy] = useState<PluginInventoryEntry['entryId'] | null>(null)
useEffect(() => {
let current = true
@@ -77,6 +80,16 @@ export function PluginInventorySettingsTab({ list, t }: PluginInventorySettingsT
return () => { current = false }
}, [list, request])
/** Toggle one entry then re-read the inventory. */
const toggle = (entryId: PluginInventoryEntry['entryId'], enabled: boolean): void => {
if (busy !== null) return
setBusy(entryId)
void setEnabled(entryId, enabled).then(
() => setRequest(value => value + 1),
() => { /* the next list reflects the unchanged state */ },
).finally(() => setBusy(null))
}
const normalizedQuery = query.trim().toLocaleLowerCase()
const filteredEntries = useMemo(
() => state.status === 'ready'
@@ -183,6 +196,15 @@ export function PluginInventorySettingsTab({ list, t }: PluginInventorySettingsT
</div>
) : null}
</dl>
<button
className={css.toggle}
type="button"
disabled={busy === entry.entryId}
aria-busy={busy === entry.entryId || undefined}
onClick={() => { toggle(entry.entryId, !entry.enabled) }}
>
{busy === entry.entryId ? t('toggling') : entry.enabled ? t('disable') : t('enable')}
</button>
</div>
) : null}
</li>
@@ -34,7 +34,13 @@ export function apply(ctx: ClientContext): void {
}
return result.value
}
const injected = (): PluginInventorySettingsTabInjected => ({ list })
const setEnabled: PluginInventorySettingsTabInjected['setEnabled'] = async (entryId, enabled) => {
const result = await ctx.remote.pluginInventory.setEnabled(entryId, enabled)
if (!result.ok) {
throw new Error(`pluginInventory.setEnabled failed: ${result.error.code}: ${result.error.message}`)
}
}
const injected = (): PluginInventorySettingsTabInjected => ({ list, setEnabled })
ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
name: 'settings.plugins.tab',
@@ -20,6 +20,9 @@ export const zh = {
active: '已挂载',
failed: '挂载失败',
unloading: '卸载中',
enable: '启用',
disable: '停用',
toggling: '切换中…',
} satisfies Record<string, string>
/** Plugin inventory locale key union. */
@@ -45,4 +48,7 @@ export const en = {
active: 'Mounted',
failed: 'Mount failed',
unloading: 'Unloading',
enable: 'Enable',
disable: 'Disable',
toggling: 'Toggling…',
} satisfies Record<PluginInventoryLocaleKey, string>