diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 27941f9eb..580e77b99 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,7 +1,8 @@ import { useState, useEffect, useRef } from "react"; -import { Settings, Cloud, CloudOff } from "lucide-react"; +import { Settings, Cloud, CloudOff, Plug } from "lucide-react"; import { Button } from "./ui/button"; import { SettingsDialog } from "./SettingsDialog"; +import { PluginsDialog } from "./PluginsDialog"; import { toast } from "sonner"; import { useCloudStatus } from "../hooks/useCloudStatus"; @@ -22,8 +23,9 @@ export function Header({ onSettingsTabOpened, }: HeaderProps) { const [settingsOpen, setSettingsOpen] = useState(false); + const [pluginsOpen, setPluginsOpen] = useState(false); const [initialTab, setInitialTab] = useState< - "general" | "account" | "api-keys" | "plugins" + "general" | "account" | "api-keys" >("general"); const [initialPluginPath, setInitialPluginPath] = useState(""); @@ -79,13 +81,15 @@ export function Header({ setSettingsOpen(true); }; - // React to external requests to open a specific settings tab + // React to external requests to open a specific settings/plugins tab useEffect(() => { if (openSettingsTab) { - setInitialTab( - openSettingsTab as "general" | "account" | "api-keys" | "plugins" - ); - setSettingsOpen(true); + if (openSettingsTab === "plugins") { + setPluginsOpen(true); + } else { + setInitialTab(openSettingsTab as "general" | "account" | "api-keys"); + setSettingsOpen(true); + } onSettingsTabOpened?.(); } }, [openSettingsTab, onSettingsTabOpened]); @@ -95,17 +99,20 @@ export function Header({ if (window.scope?.onDeepLinkAction) { return window.scope.onDeepLinkAction(data => { if (data.action === "install-plugin" && data.package) { - setInitialTab("plugins"); setInitialPluginPath(data.package); - setSettingsOpen(true); + setPluginsOpen(true); } }); } }, []); - const handleClose = () => { + const handleSettingsClose = () => { setSettingsOpen(false); setInitialTab("general"); + }; + + const handlePluginsClose = () => { + setPluginsOpen(false); setInitialPluginPath(""); }; @@ -141,6 +148,15 @@ export function Header({ )} + + + ) : plugins.length === 0 ? ( +

+ {debouncedSearch + ? "No plugins found matching your search." + : "No plugins available."} +

+ ) : ( +
+ {plugins.map(plugin => { + const isInstalled = plugin.repositoryUrl + ? installedSet.has(normalizeRepoUrl(plugin.repositoryUrl)) + : false; + const daydreamUrl = + plugin.creatorUsername && plugin.slug + ? `${DAYDREAM_APP_BASE}/plugins/${plugin.creatorUsername}/${plugin.slug}` + : plugin.learnMoreUrl || + `${DAYDREAM_APP_BASE}/plugins?search=${encodeURIComponent(plugin.name)}`; + return ( +
+ {plugin.iconUrl ? ( + + ) : ( +
+ + {plugin.name.charAt(0).toUpperCase()} + +
+ )} +
+
+ + {plugin.name} + + {plugin.version && ( + + v{plugin.version} + + )} +
+ {plugin.description && ( +

+ {plugin.description} +

+ )} + {plugin.tags.length > 0 && ( +
+ {plugin.tags.slice(0, 4).map(tag => ( + + {tag} + + ))} +
+ )} +
+
+ + {plugin.repositoryUrl && ( + + )} + {isInstalled ? ( + + Installed + + ) : plugin.repositoryUrl ? ( + + ) : null} +
+
+ ); + })} +
+ )} + + ); +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index eb0aeee9d..e4b864578 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -1,3 +1,17 @@ +// Electron preload exposes scope API on window +interface ScopeAPI { + browseDirectory?: (title: string) => Promise; + onDeepLinkAction?: ( + callback: (data: { action: string; package: string }) => void + ) => () => void; +} + +declare global { + interface Window { + scope?: ScopeAPI; + } +} + // Pipeline IDs are dynamic - any string returned from backend is valid export type PipelineId = string;