diff --git a/src/context-menu/TrayContextMenu.tsx b/src/context-menu/TrayContextMenu.tsx index d3db911e..25f86088 100644 --- a/src/context-menu/TrayContextMenu.tsx +++ b/src/context-menu/TrayContextMenu.tsx @@ -55,6 +55,34 @@ export async function handleInstall( } } +export async function installLibrarySilently(app: any, libraryName: string): Promise { + const normalized = normalizeLibraryName(libraryName); + + const installPromise = requestAPI('library/install', { + method: 'POST', + body: JSON.stringify({ libraryName: normalized }) + }); + + Notification.promise(installPromise, { + pending: { message: `Installing ${libraryName} library...`, options: { autoClose: 3000 } }, + success: { message: () => `Library ${libraryName} installed successfully.`, options: { autoClose: 3000 } }, + error: { message: (err) => `Failed to install ${libraryName}: ${err}`, options: { autoClose: false } } + }); + + try { + const res = await installPromise; + if (res.status === 'OK') { + await app.commands.execute(commandIDs.refreshComponentList); + return true; + } + console.error(`Installation failed: ${res.error || 'Unknown error'}`); + return false; + } catch (e) { + console.error('Installation error:', e); + return false; + } +} + export interface TrayContextMenuProps { app: any; x: number; diff --git a/src/helpers/notificationEffects.ts b/src/helpers/notificationEffects.ts index e3592f95..e140763a 100644 --- a/src/helpers/notificationEffects.ts +++ b/src/helpers/notificationEffects.ts @@ -65,7 +65,7 @@ function pathToLibraryId(rawPath?: string | null): string | null { return normalizeLibraryName(m[1]); } -async function loadLibraryIndex(): Promise> { +export async function loadLibraryIndex(): Promise> { const res: any = await requestAPI('library/get_config', { method: 'GET' }); const libs = res?.config?.libraries; if (!Array.isArray(libs)) throw new Error('Invalid library response'); diff --git a/src/index.tsx b/src/index.tsx index 1853ae69..25a6f769 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -33,8 +33,9 @@ import type { Signal } from "@lumino/signaling"; import { commandIDs } from "./commands/CommandIDs"; import { IEditorTracker } from '@jupyterlab/fileeditor'; import { IMainMenu } from '@jupyterlab/mainmenu'; -import { handleInstall } from './context-menu/TrayContextMenu'; - +import { installLibrarySilently } from './context-menu/TrayContextMenu'; +import { normalizeLibraryName } from './tray_library/ComponentLibraryConfig'; +import { loadLibraryIndex } from './helpers/notificationEffects'; import { installComponentPreview } from './component_info_sidebar/previewHelper'; const FACTORY = 'Xircuits editor'; @@ -578,22 +579,17 @@ const xircuits: JupyterFrontEndPlugin = { }); } - async function getInstalledLibraries(): Promise> { - try { - const result = await requestAPI('library/get_config', { - method: 'GET' - }); - - return new Set( - result.config.libraries - .filter((lib: any) => lib.status === 'installed' && typeof lib.name === 'string') - .map((lib: any) => lib.name) - ); - } catch (err) { - console.error('Failed to load library config via API:', err); - return new Set(); + async function getInstalledIds(): Promise> { + const idx = await loadLibraryIndex(); + const set = new Set(); + for (const [id, entry] of idx) { + if (String(entry.status).toLowerCase() === 'installed') { + set.add(id); + } } + return set; } + app.commands.addCommand(commandIDs.fetchExamples, { label: 'Fetch Example Workflows', caption: 'Fetch example workflows into the examples directory', @@ -615,24 +611,32 @@ const xircuits: JupyterFrontEndPlugin = { icon: xircuitsIcon, execute: async () => { const currentPath = browserFactory.tracker.currentWidget?.model.path ?? ''; - const installedLibs = await getInstalledLibraries(); + const installedIds = await getInstalledIds(); - for (const lib of libraries) { - if (installedLibs.has(lib)) { - console.log(`Library ${lib} already installed. Skipping.`); - continue; - } + const pairs = libraries.map(lib => ({ + raw: lib, + id: normalizeLibraryName(lib) + })); - const ok = await handleInstall(app, lib, () => - app.commands.execute(commandIDs.refreshComponentList) - ); + const missing = pairs.filter(p => !installedIds.has(p.id)); + if (missing.length) { + const list = missing.map(p => p.raw).join(', '); + const ok = window.confirm(`This workflow template requires the following component libraries: ${list}. Would you like to install them now?`); + if (!ok) { + console.warn('User cancelled installation.'); + return; + } + + for (const { raw, id } of missing) { + const ok = await installLibrarySilently(app, raw); if (!ok) { - console.warn(`Aborted: ${lib} not installed.`); + console.warn(`Aborted: ${raw} not installed.`); return; } - installedLibs.add(lib); + installedIds.add(id); } + } // Currently the templates are stored at the `examples` dir await app.commands.execute(commandIDs.fetchExamples);