feat(frontend): host les fenêtres plugin et l'API publique windows.open
Ajoute la vue window host qui charge le layout plugin dans une fenêtre OS détachée, expose react/react-dom/jsx-runtime hébergés sous frontend/public/plugin-host pour que le runtime SDK y résolve React, et câble services.windows.open() côté runtime plugin (loader/services/registry). npm test -- --run src/adapters/window.test.ts src/app/ViewWindow.test.tsx src/plugins/runtime/loader.test.ts src/plugins/runtime/services.test.ts : 39/39 npm run typecheck : OK npm run build : OK Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -15,6 +15,12 @@
|
||||
* collected, never thrown past `loadPlugins`.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import * as ReactDomClient from "react-dom/client";
|
||||
import * as ReactJsxRuntime from "react/jsx-runtime";
|
||||
import * as ReactJsxDevRuntime from "react/jsx-dev-runtime";
|
||||
|
||||
import type { JsonValue, PluginContributionDto, PluginRuntimePlugin } from "@/domain";
|
||||
import {
|
||||
PluginCommandRegistry,
|
||||
@ -93,6 +99,7 @@ export interface PluginLoadOptions {
|
||||
}
|
||||
|
||||
const DEFAULT_PLUGIN_LOAD_TIMEOUT_MS = 10_000;
|
||||
let hostReactImportMapInstalled = false;
|
||||
|
||||
async function withTimeout<T>(
|
||||
promise: Promise<T>,
|
||||
@ -161,6 +168,27 @@ function nonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function installHostReactImportMap(): void {
|
||||
if (hostReactImportMapInstalled) {
|
||||
hostReactImportMapInstalled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const globalHost = globalThis as unknown as {
|
||||
__IDEA_PLUGIN_HOST_REACT__?: typeof React;
|
||||
__IDEA_PLUGIN_HOST_REACT_DOM__?: typeof ReactDom;
|
||||
__IDEA_PLUGIN_HOST_REACT_DOM_CLIENT__?: typeof ReactDomClient;
|
||||
__IDEA_PLUGIN_HOST_REACT_JSX_RUNTIME__?: typeof ReactJsxRuntime;
|
||||
__IDEA_PLUGIN_HOST_REACT_JSX_DEV_RUNTIME__?: typeof ReactJsxDevRuntime;
|
||||
};
|
||||
globalHost.__IDEA_PLUGIN_HOST_REACT__ = React;
|
||||
globalHost.__IDEA_PLUGIN_HOST_REACT_DOM__ = ReactDom;
|
||||
globalHost.__IDEA_PLUGIN_HOST_REACT_DOM_CLIENT__ = ReactDomClient;
|
||||
globalHost.__IDEA_PLUGIN_HOST_REACT_JSX_RUNTIME__ = ReactJsxRuntime;
|
||||
globalHost.__IDEA_PLUGIN_HOST_REACT_JSX_DEV_RUNTIME__ = ReactJsxDevRuntime;
|
||||
hostReactImportMapInstalled = true;
|
||||
}
|
||||
|
||||
function safePluginId(entry: unknown): string {
|
||||
return nonEmptyString(objectOrEmpty(entry).id) ?? "<unknown-plugin>";
|
||||
}
|
||||
@ -247,6 +275,7 @@ async function loadOne(
|
||||
throw new Error("missing plugin bundle URL");
|
||||
}
|
||||
|
||||
installHostReactImportMap();
|
||||
const mod = resolveIdeaPluginModule(
|
||||
await withTimeout(
|
||||
import(/* @vite-ignore */ bundleUrl),
|
||||
@ -282,8 +311,11 @@ async function loadOne(
|
||||
menu,
|
||||
storage,
|
||||
};
|
||||
if (hasCapability(entry, "tooling")) {
|
||||
ctx.services = createPluginServices(gateways);
|
||||
if (hasCapability(entry, "tooling") || hasCapability(entry, "ui")) {
|
||||
ctx.services = createPluginServices(gateways, {
|
||||
pluginId,
|
||||
declaredLayoutTypes,
|
||||
});
|
||||
}
|
||||
|
||||
activation = await withTimeout(
|
||||
|
||||
Reference in New Issue
Block a user