feat(plugins): let plugin tool windows use an SVG icon from the package (#1363)

A view's `icon` could only be a built-in icon name, so a plugin's tool
window button couldn't show the plugin's own mark. Like `fileIcons`, an
`icon` ending in `.svg` is now a path in the package: the host reads and
checks it at install time (a missing file or an SVG with `<script>` refuses
the install), serves it as the `viewIcon/<id>` asset, and the tool window
registers it as an external icon, falling back to the plugin icon if it
can't be loaded. Icon names work as before.

Closes #1335

Co-authored-by: yzPeedro <[email protected]>
Цей коміт міститься в:
Pedro Pessoa authored and GitHub committed 2026-11-05 07:48:39 +08:00
1 джерело ab2b53ff80
коміт 7ffaa0b5da
4 files changed
+116 -31

No files matched your search

+44 -14
Переглянути файл
@@ -290,6 +290,49 @@ function projectFileIcon(pluginId: string, decl: FileIconDecl): () => void {
export const pluginToolWindowId = (pluginId: string, view: string) => `plugin.${pluginId}.${view}`;
function registerView(pluginId: string, decl: ViewDecl, icon: string): () => void {
return registerToolWindow({
id: pluginToolWindowId(pluginId, decl.id),
title: decl.title,
icon,
anchor: decl.location,
order: 900,
component: () =>
decl.web ? (
<WebView pluginId={pluginId} view={decl.id} label={decl.title} />
) : (
<PluginView pluginId={pluginId} view={decl.id} label={decl.title} />
),
});
}
function projectView(pluginId: string, decl: ViewDecl): () => void {
if (!decl.icon?.endsWith(".svg")) return registerView(pluginId, decl, decl.icon ?? "plugin");
let disposed = false;
let undo = () => {};
void pluginsBackend()
.assets(pluginId, `viewIcon/${decl.id}`)
.then((assets) => {
if (disposed) return;
const svg = assets.find(([id]) => id === `viewIcon/${decl.id}`)?.[1];
const name = `ext:${pluginId}/view-${decl.id}`;
const unregister = svg ? registerExternalIcons([[name, svg]]) : () => {};
const remove = registerView(pluginId, decl, svg ? name : "plugin");
undo = () => {
remove();
unregister();
};
})
.catch((e: unknown) => {
console.warn(`plugin ${pluginId}: view icon ${decl.id}`, e);
if (!disposed) undo = registerView(pluginId, decl, "plugin");
});
return () => {
disposed = true;
undo();
};
}
/** Registers the client side of the P7 points; returns their disposer. */
export function defineViewUiPoints(): () => void {
const disposers = [
@@ -326,20 +369,7 @@ export function defineViewUiPoints(): () => void {
}),
defineUiPoint<ViewDecl>("views", {
title: "Tool Windows",
project: ({ pluginId, decl }) =>
registerToolWindow({
id: pluginToolWindowId(pluginId, decl.id),
title: decl.title,
icon: decl.icon ?? "plugin",
anchor: decl.location,
order: 900,
component: () =>
decl.web ? (
<WebView pluginId={pluginId} view={decl.id} label={decl.title} />
) : (
<PluginView pluginId={pluginId} view={decl.id} label={decl.title} />
),
}),
project: ({ pluginId, decl }) => projectView(pluginId, decl),
}),
defineUiPoint<CustomEditorDecl>("customEditors", {
title: "Editors",