Add status bar contribution host

This commit is contained in:
2026-06-27 13:21:22 +08:00
parent 7630a31286
commit 6cc37972d1
7 changed files with 210 additions and 14 deletions
+33
View File
@@ -0,0 +1,33 @@
import { test, expect } from '@playwright/test';
import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js';
test.describe('Status Bar host', () => {
let consoleCollector;
test.beforeEach(async ({ page }) => {
consoleCollector = setupConsoleCollector(page);
await resetMockState(page);
await page.goto('/');
await waitForAppReady(page);
});
test.afterEach(async () => {
consoleCollector.assertNoErrors();
});
test('renders enabled plugin statusBarItems', async ({ page }) => {
const statusBar = page.locator('.status-bar');
await expect(statusBar).toBeVisible();
await expect(statusBar.locator('[data-status-item-id="verstak.platform-test.status"]')).toContainText('All Tests Pass');
});
test('refreshes statusBarItems after disabling plugin', async ({ page }) => {
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
await expect(page.locator('[data-status-item-id="verstak.platform-test.status"]')).toBeVisible();
await pluginCard.locator('button.btn-disable').click();
await expect(pluginCard.locator('button.btn-enable')).toBeVisible({ timeout: 10000 });
await expect(page.locator('[data-status-item-id="verstak.platform-test.status"]')).not.toBeVisible();
});
});
+22 -10
View File
@@ -2,6 +2,7 @@
import PluginManager from './lib/plugin-manager/PluginManager.svelte';
import Sidebar from './lib/shell/Sidebar.svelte';
import CommandPalette from './lib/shell/CommandPalette.svelte';
import StatusBar from './lib/shell/StatusBar.svelte';
import ViewContainer from './lib/shell/ViewContainer.svelte';
import VaultSelection from './lib/shell/VaultSelection.svelte';
import WorkbenchHost from './lib/shell/WorkbenchHost.svelte';
@@ -295,16 +296,19 @@
<Sidebar />
<CommandPalette />
<section class="content scroll-surface">
{#if currentView === 'plugin-manager'}
<PluginManager {activeSettingsPluginId} {activeSettingsPanelId} />
{:else if currentView === 'workbench'}
<WorkbenchHost {openedResource} />
{:else if currentView === 'workspace'}
<WorkspaceHost selectedWorkspaceName={selectedWorkspaceName} nodes={workspaceNodes} />
{:else}
<ViewContainer {activeView} {activeViewPluginId} />
{/if}
<section class="content-shell">
<section class="content scroll-surface">
{#if currentView === 'plugin-manager'}
<PluginManager {activeSettingsPluginId} {activeSettingsPanelId} />
{:else if currentView === 'workbench'}
<WorkbenchHost {openedResource} />
{:else if currentView === 'workspace'}
<WorkspaceHost selectedWorkspaceName={selectedWorkspaceName} nodes={workspaceNodes} />
{:else}
<ViewContainer {activeView} {activeViewPluginId} />
{/if}
</section>
<StatusBar />
</section>
</main>
{/if}
@@ -460,6 +464,14 @@
overflow: hidden;
}
.content-shell {
flex: 1;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
}
.content {
flex: 1;
min-width: 0;
+102
View File
@@ -0,0 +1,102 @@
<script>
import { onDestroy, onMount } from 'svelte';
import * as App from '../../../wailsjs/go/api/App';
let items = [];
$: leftItems = items.filter((item) => item.position === 'left');
$: centerItems = items.filter((item) => item.position === 'center');
$: rightItems = items.filter((item) => item.position === 'right');
const inactiveStatuses = new Set(['disabled', 'failed', 'incompatible', 'missing-required-capability']);
async function loadStatusBar() {
const [plugins, contributions] = await Promise.all([
App.GetPlugins().catch(() => []),
App.GetContributions().catch(() => ({})),
]);
const pluginById = new Map((plugins || []).map((plugin) => [plugin.manifest?.id, plugin]));
items = (contributions.statusBarItems || [])
.filter((item) => {
const plugin = pluginById.get(item.pluginId);
if (!plugin) return false;
return !inactiveStatuses.has(plugin.status);
})
.map((item) => ({
...item,
position: item.position || 'left',
}));
}
onMount(() => {
loadStatusBar();
window.addEventListener('verstak:plugins-changed', loadStatusBar);
});
onDestroy(() => {
window.removeEventListener('verstak:plugins-changed', loadStatusBar);
});
</script>
<footer class="status-bar" aria-label="Status bar">
<div class="status-bar-group status-left">
{#each leftItems as item}
<span class="status-bar-item" data-status-item-id={item.id} title={item.pluginId}>
{item.label || item.id}
</span>
{/each}
</div>
<div class="status-bar-group status-center">
{#each centerItems as item}
<span class="status-bar-item" data-status-item-id={item.id} title={item.pluginId}>
{item.label || item.id}
</span>
{/each}
</div>
<div class="status-bar-group status-right">
{#each rightItems as item}
<span class="status-bar-item" data-status-item-id={item.id} title={item.pluginId}>
{item.label || item.id}
</span>
{/each}
</div>
</footer>
<style>
.status-bar {
min-height: 1.7rem;
display: grid;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
align-items: center;
gap: 0.5rem;
padding: 0.2rem 0.65rem;
border-top: 1px solid #16213e;
background: #111629;
color: #9fb2ca;
font-size: 0.74rem;
}
.status-bar-group {
min-width: 0;
display: flex;
align-items: center;
gap: 0.4rem;
overflow: hidden;
}
.status-center {
justify-content: center;
}
.status-right {
justify-content: flex-end;
}
.status-bar-item {
max-width: 18rem;
overflow: hidden;
padding: 0.12rem 0.35rem;
border-radius: 4px;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>