Archived
feat: support plugin mobile tab icons
This commit is contained in:
@@ -1021,7 +1021,7 @@ export class PiWebApp extends LitElement {
|
||||
{ id: "navigation", label: "Sessions", icon: "navigation", className: "navigation-tab" },
|
||||
{ id: "chat", label: "Chat", icon: "chat" },
|
||||
...this.visibleWorkspacePanels().map((panel): AppMobileMainTab => {
|
||||
const icon = this.mobilePanelIcon(panel);
|
||||
const icon = panel.icon ?? this.mobilePanelIcon(panel);
|
||||
return {
|
||||
id: panel.id,
|
||||
label: panel.title,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { LitElement, css, html, svg } from "lit";
|
||||
import { LitElement, css, html, svg, type TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import type { AppState } from "../../appState";
|
||||
|
||||
export type AppMobileMainTabIcon = "navigation" | "chat" | "files" | "git" | "terminal";
|
||||
export type AppMobileMainTabBuiltinIcon = "navigation" | "chat" | "files" | "git" | "terminal";
|
||||
export type AppMobileMainTabIcon = AppMobileMainTabBuiltinIcon | TemplateResult;
|
||||
|
||||
export interface AppMobileMainTab {
|
||||
id: AppState["mainView"];
|
||||
@@ -115,6 +116,7 @@ export class AppMobileMainTabs extends LitElement {
|
||||
}
|
||||
|
||||
private renderIcon(icon: AppMobileMainTabIcon) {
|
||||
if (typeof icon !== "string") return html`<span class="tab-custom-icon" aria-hidden="true">${icon}</span>`;
|
||||
switch (icon) {
|
||||
case "navigation":
|
||||
return svg`
|
||||
@@ -206,6 +208,8 @@ export class AppMobileMainTabs extends LitElement {
|
||||
.mobile-tabs .navigation-tab { display: none; }
|
||||
.mobile-tabs button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); }
|
||||
.tab-icon { flex: 0 0 auto; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
|
||||
.tab-custom-icon { flex: 0 0 auto; width: 18px; height: 18px; display: inline-grid; place-items: center; color: currentColor; pointer-events: none; }
|
||||
.tab-custom-icon svg { width: 18px; height: 18px; pointer-events: none; }
|
||||
.tab-fallback { display: none; font-weight: 650; letter-spacing: .01em; pointer-events: none; }
|
||||
.tab-label { min-width: 0; }
|
||||
.tab-badge { flex: 0 0 auto; display: inline-block; min-width: 14px; margin-left: 0; border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 0 5px; font-size: 11px; line-height: 16px; text-align: center; }
|
||||
|
||||
@@ -51,6 +51,31 @@ describe("PluginRegistry", () => {
|
||||
expect(registry.getWorkspacePanels().map((panel) => panel.id)).toEqual(["core:workspace.files", "core:workspace.git", "core:workspace.terminal"]);
|
||||
});
|
||||
|
||||
it("provides html and svg helpers to plugin activation", () => {
|
||||
const registry = new PluginRegistry();
|
||||
registry.register({
|
||||
id: "example",
|
||||
plugin: {
|
||||
apiVersion: 1,
|
||||
name: "Example",
|
||||
activate: ({ html, svg }) => ({
|
||||
contributions: {
|
||||
workspacePanels: [
|
||||
{
|
||||
id: "workspace.logs",
|
||||
title: "Logs",
|
||||
icon: svg`<svg viewBox="0 0 24 24"><path d="M4 6h16"></path></svg>`,
|
||||
render: () => html`<p>Logs</p>`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.getWorkspacePanels()[0]?.icon).toBeDefined();
|
||||
});
|
||||
|
||||
it("rejects duplicate ids within the same namespace", () => {
|
||||
const registry = new PluginRegistry();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { html } from "lit";
|
||||
import { html, svg } from "lit";
|
||||
import type { AppState } from "../appState";
|
||||
import type { Workspace } from "../api";
|
||||
import type { PiWebPluginRegistration, PluginAction, PluginRuntimeContext, QualifiedContributionId, QualifiedPluginAction, QualifiedThemeContribution, QualifiedThemePairContribution, QualifiedWorkspaceLabelContribution, QualifiedWorkspacePanelContribution, ThemeContribution, ThemePairContribution, WorkspaceLabelContribution, WorkspaceLabelItem, WorkspacePanelContext, WorkspacePanelContribution } from "./types";
|
||||
@@ -31,7 +31,7 @@ export class PluginRegistry {
|
||||
|
||||
const apiVersion: unknown = plugin.apiVersion;
|
||||
if (apiVersion !== 1) throw new Error(`Unsupported plugin API version for ${id}: ${String(apiVersion)}`);
|
||||
const result = plugin.activate({ apiVersion: 1, pluginId: id, html });
|
||||
const result = plugin.activate({ apiVersion: 1, pluginId: id, html, svg });
|
||||
const contributions = result.contributions;
|
||||
for (const action of contributions.actions ?? []) this.actions.push(this.qualifyAction(id, action));
|
||||
for (const panel of contributions.workspacePanels ?? []) this.workspacePanels.push(this.qualifyWorkspacePanel(id, panel));
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { LocalContributionId, PluginId, QualifiedContributionId } from "./i
|
||||
|
||||
export type { LocalContributionId, PluginId, QualifiedContributionId } from "./ids";
|
||||
export type HtmlTemplateTag = (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult;
|
||||
export type SvgTemplateTag = (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult;
|
||||
|
||||
export interface PiWebPluginRegistration {
|
||||
id: PluginId;
|
||||
@@ -23,6 +24,7 @@ export interface PluginActivationContext {
|
||||
apiVersion: 1;
|
||||
pluginId: PluginId;
|
||||
html: HtmlTemplateTag;
|
||||
svg: SvgTemplateTag;
|
||||
}
|
||||
|
||||
export interface PluginActivationResult {
|
||||
@@ -118,9 +120,12 @@ export interface WorkspacePanelContext {
|
||||
onSelectTerminal: (terminalId: string | undefined, options?: { replace?: boolean | undefined }) => void;
|
||||
}
|
||||
|
||||
export type WorkspacePanelIcon = TemplateResult;
|
||||
|
||||
export interface WorkspacePanelContribution {
|
||||
id: LocalContributionId;
|
||||
title: string;
|
||||
icon?: WorkspacePanelIcon;
|
||||
order?: number;
|
||||
visible?: (context: WorkspacePanelVisibilityContext) => boolean;
|
||||
badge?: (context: WorkspacePanelContext) => string | number | TemplateResult | undefined;
|
||||
|
||||
Reference in New Issue
Block a user