feat(FN-3093): address plugin manager responsive overflow
The merge fixes responsive overflow issues in the PluginManager component by adding 15 lines of CSS. Fusion-Task-Id: FN-3093
This commit is contained in:
@@ -170,12 +170,19 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.plugin-homepage {
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.plugin-homepage a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
color: var(--color-info);
|
||||
font-size: 0.85rem;
|
||||
flex-wrap: wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.plugin-detail-section-heading {
|
||||
@@ -258,6 +265,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
row-gap: var(--space-xs);
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -312,6 +321,12 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.plugin-bundled-runtime-status {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-bundled-runtime-status--installed {
|
||||
background: var(--status-done-bg);
|
||||
color: var(--done);
|
||||
@@ -323,6 +338,11 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.plugin-manager,
|
||||
.plugin-manager-detail {
|
||||
padding-inline: var(--space-sm);
|
||||
}
|
||||
|
||||
.plugin-manager-detail-header {
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,12 @@ interface BundledRuntimePlugin {
|
||||
}
|
||||
|
||||
const BUNDLED_RUNTIME_PLUGINS: BundledRuntimePlugin[] = [
|
||||
{
|
||||
id: "fusion-plugin-agent-browser-runtime",
|
||||
name: "Agent Browser Runtime",
|
||||
path: "./plugins/fusion-plugin-agent-browser-runtime",
|
||||
experimental: true,
|
||||
},
|
||||
{
|
||||
id: "fusion-plugin-hermes-runtime",
|
||||
name: "Hermes Runtime",
|
||||
@@ -351,6 +357,7 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
</label>
|
||||
{schema.type === "string" && !schema.multiline && (
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
id={`setting-${key}`}
|
||||
value={(pluginSettings[key] as string) ?? ""}
|
||||
@@ -361,6 +368,7 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
)}
|
||||
{schema.type === "string" && schema.multiline && (
|
||||
<textarea
|
||||
className="input"
|
||||
id={`setting-${key}`}
|
||||
rows={4}
|
||||
value={(pluginSettings[key] as string) ?? ""}
|
||||
@@ -371,6 +379,7 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
)}
|
||||
{schema.type === "password" && (
|
||||
<input
|
||||
className="input"
|
||||
type="password"
|
||||
id={`setting-${key}`}
|
||||
value={(pluginSettings[key] as string) ?? ""}
|
||||
@@ -381,6 +390,7 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
)}
|
||||
{schema.type === "number" && (
|
||||
<input
|
||||
className="input"
|
||||
type="number"
|
||||
id={`setting-${key}`}
|
||||
value={(pluginSettings[key] as number) ?? ""}
|
||||
@@ -400,6 +410,7 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
)}
|
||||
{schema.type === "enum" && (
|
||||
<select
|
||||
className="select"
|
||||
id={`setting-${key}`}
|
||||
value={(pluginSettings[key] as string) ?? ""}
|
||||
onChange={(e) => setPluginSettings({ ...pluginSettings, [key]: e.target.value })}
|
||||
@@ -416,6 +427,7 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
{(pluginSettings[key] as unknown[] | undefined)?.map((item, index) => (
|
||||
<div key={index} className="plugin-settings-array-item">
|
||||
<input
|
||||
className="input"
|
||||
type={schema.itemType === "number" ? "number" : "text"}
|
||||
value={(item as string | number) ?? ""}
|
||||
onChange={(e) => {
|
||||
@@ -497,11 +509,11 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
}
|
||||
|
||||
const installedPluginIds = new Set(plugins.map((plugin) => plugin.id));
|
||||
const installedPluginsById = new Map(plugins.map((plugin) => [plugin.id, plugin]));
|
||||
|
||||
// Bundled runtime plugin IDs — these are shown in the dedicated section below,
|
||||
// so we exclude them from the main plugin list to avoid duplication.
|
||||
const bundledPluginIds = new Set(BUNDLED_RUNTIME_PLUGINS.map((p) => p.id));
|
||||
const userInstalledPlugins = plugins.filter((p) => !bundledPluginIds.has(p.id));
|
||||
// Keep bundled plugins in the main list once installed so users can always
|
||||
// access enable/disable, settings, and uninstall controls.
|
||||
const installedPlugins = plugins;
|
||||
|
||||
const renderBundledRuntimeSection = () => (
|
||||
<section className="plugin-bundled-runtime-section" aria-label="Bundled Runtime Plugins">
|
||||
@@ -529,11 +541,20 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
</div>
|
||||
<button
|
||||
className={`btn ${isInstalled ? "btn-secondary" : "btn-primary"} btn-sm`}
|
||||
onClick={() => handleInstallBundledRuntimePlugin(bundledPlugin)}
|
||||
disabled={isInstalled || installingBundledPluginId === bundledPlugin.id}
|
||||
onClick={() => {
|
||||
if (isInstalled) {
|
||||
const installedPlugin = installedPluginsById.get(bundledPlugin.id);
|
||||
if (installedPlugin) {
|
||||
void handleSelectPlugin(installedPlugin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
void handleInstallBundledRuntimePlugin(bundledPlugin);
|
||||
}}
|
||||
disabled={installingBundledPluginId === bundledPlugin.id}
|
||||
>
|
||||
{isInstalled
|
||||
? "Installed"
|
||||
? "Manage"
|
||||
: installingBundledPluginId === bundledPlugin.id
|
||||
? "Installing..."
|
||||
: `Install ${bundledPlugin.name}`}
|
||||
@@ -592,7 +613,7 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
<div className="settings-empty-state">Loading plugins...</div>
|
||||
) : (
|
||||
<>
|
||||
{userInstalledPlugins.length === 0 ? (
|
||||
{installedPlugins.length === 0 ? (
|
||||
<div className="settings-empty-state">
|
||||
<Package size={32} className="text-muted" />
|
||||
<p>No plugins installed.</p>
|
||||
@@ -600,7 +621,7 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="plugin-list">
|
||||
{userInstalledPlugins.map((plugin) => (
|
||||
{installedPlugins.map((plugin) => (
|
||||
<div key={plugin.id} className="plugin-item">
|
||||
<div className="plugin-info">
|
||||
<span className="plugin-name">{plugin.name}</span>
|
||||
|
||||
@@ -226,6 +226,7 @@ describe("PluginManager", () => {
|
||||
});
|
||||
|
||||
expect(screen.getByText("No plugins installed.")).toBeTruthy();
|
||||
expect(screen.getByText("Agent Browser Runtime")).toBeTruthy();
|
||||
expect(screen.getByText("Hermes Runtime")).toBeTruthy();
|
||||
expect(screen.getByText("Paperclip Runtime")).toBeTruthy();
|
||||
expect(screen.getByText("OpenClaw Runtime")).toBeTruthy();
|
||||
@@ -503,7 +504,7 @@ describe("PluginManager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows installed status and disables bundled runtime install button when already installed", async () => {
|
||||
it("keeps bundled plugin manageable after install", async () => {
|
||||
vi.mocked(fetchPlugins).mockResolvedValueOnce([
|
||||
{
|
||||
...mockPlugins[0],
|
||||
@@ -521,9 +522,17 @@ describe("PluginManager", () => {
|
||||
const hermesCard = screen.getByText("Hermes Runtime").closest(".plugin-bundled-runtime-item");
|
||||
expect(hermesCard).toBeTruthy();
|
||||
|
||||
const installButton = within(hermesCard as HTMLElement).getByRole("button", { name: /^Installed$/i });
|
||||
expect(installButton).toBeDisabled();
|
||||
const manageButton = within(hermesCard as HTMLElement).getByRole("button", { name: /^Manage$/i });
|
||||
expect(manageButton).not.toBeDisabled();
|
||||
expect(within(hermesCard as HTMLElement).getAllByText("Installed").length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
await userEvent.click(manageButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchPluginSettings).toHaveBeenCalledWith("fusion-plugin-hermes-runtime", undefined);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("plugin-manager-detail")).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("SSE Live Updates", () => {
|
||||
|
||||
Reference in New Issue
Block a user