diff --git a/frontend/src/components/simulator/LibraryManagerModal.tsx b/frontend/src/components/simulator/LibraryManagerModal.tsx index d94719e4..4dd3bf6e 100644 --- a/frontend/src/components/simulator/LibraryManagerModal.tsx +++ b/frontend/src/components/simulator/LibraryManagerModal.tsx @@ -4,6 +4,8 @@ import { searchLibraries, installLibrary, getInstalledLibraries, + getCustomLibraries, + deleteCustomLibrary, uninstallLibrary, } from '../../services/libraryService'; import type { ArduinoLibrary, InstalledLibrary } from '../../services/libraryService'; @@ -66,8 +68,16 @@ export const LibraryManagerModal: React.FC = ({ isOpen const fetchInstalled = useCallback(async () => { setLoadingInstalled(true); try { - const libs = await getInstalledLibraries(); - setInstalledLibraries(libs); + // P2.2c — the shared global list (index libs) PLUS the user's per-user + // custom uploads (which live in their per-user store, not the global + // list). Custom first so they're easy to find; de-duped by name. + const [libs, custom] = await Promise.all([getInstalledLibraries(), getCustomLibraries()]); + const customNames = new Set(custom.map((c) => (c.name || '').toLowerCase())); + const merged = [ + ...custom, + ...libs.filter((l) => !customNames.has((l.library?.name || l.name || '').toLowerCase())), + ]; + setInstalledLibraries(merged); } catch (e: unknown) { setStatusMsg({ type: 'error', @@ -166,6 +176,25 @@ export const LibraryManagerModal: React.FC = ({ isOpen } }; + // P2.2c — a CUSTOM lib lives in the user's per-user store, not the shared + // arduino-cli dir, so removing it hits the per-user delete endpoint. + const handleRemoveCustom = async (libName: string) => { + setUninstallingLib(libName); + setStatusMsg(null); + try { + const result = await deleteCustomLibrary(libName); + if (result.success) { + setStatusMsg({ type: 'success', text: `Removed your custom "${libName}".` }); + removeFromManifest(libName); + fetchInstalled(); + } else { + setStatusMsg({ type: 'error', text: result.error || `Failed to remove "${libName}"` }); + } + } finally { + setUninstallingLib(null); + } + }; + // ── Project manifest (velxio.json) editing ────────────────────────────── const declared = manifestLibs ?? []; @@ -748,10 +777,19 @@ export const LibraryManagerModal: React.FC = ({ isOpen )} diff --git a/frontend/src/services/libraryService.ts b/frontend/src/services/libraryService.ts index b314544c..5ccc4d6e 100644 --- a/frontend/src/services/libraryService.ts +++ b/frontend/src/services/libraryService.ts @@ -32,6 +32,8 @@ export interface InstalledLibrary { version?: string; author?: string; sentence?: string; + /** P2.2c — true for the user's own per-user custom uploads (vs shared index libs). */ + custom?: boolean; } export async function searchLibraries(query: string): Promise { @@ -74,6 +76,42 @@ export async function getInstalledLibraries(): Promise { return data.libraries || []; } +/** + * P2.2c — the signed-in user's per-user CUSTOM libraries (uploaded .zip), + * which live in their per-user store, not the shared global list. Merged into + * the Installed view + velxio.json autocomplete so they can see and reuse their + * uploads. Returns [] when unauthenticated or no pro overlay (401/404). + */ +export async function getCustomLibraries(): Promise { + try { + const res = await fetch('/api/pro/libraries/custom', { credentials: 'include' }); + if (!res.ok) return []; + const data = await res.json(); + return Array.isArray(data) ? data : []; + } catch { + return []; + } +} + +/** P2.2c — remove one of the user's own custom uploads (per-user store). */ +export async function deleteCustomLibrary( + name: string, +): Promise<{ success: boolean; error?: string }> { + try { + const res = await fetch(`/api/pro/libraries/custom/${encodeURIComponent(name)}`, { + method: 'DELETE', + credentials: 'include', + }); + if (!res.ok) { + const err = await res.json().catch(() => ({ detail: 'Failed to remove' })); + return { success: false, error: err.detail || 'Failed to remove' }; + } + return { success: true }; + } catch (e) { + return { success: false, error: e instanceof Error ? e.message : 'network error' }; + } +} + /** * Resolve a library name to "Name@X.Y.Z" using arduino-cli lib search. * Returns null if the library is not found in the index.