feat(P2.2c): show per-user custom libs in the Library Manager + autocomplete
The Library Manager Installed tab + the velxio.json add-autocomplete now merge the user's per-user custom uploads (getCustomLibraries -> GET /api/pro/libraries/ custom) with the shared global index list, so users can see and reuse their own uploads (which live in the per-user store, not the global list). A custom lib's button removes it via the per-user delete endpoint (not arduino-cli uninstall, which would not find it). Degrades to [] for OSS/anon.
This commit is contained in:
parent
cc40bda3eb
commit
97f390719f
|
|
@ -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<LibraryManagerModalProps> = ({ 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<LibraryManagerModalProps> = ({ 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<LibraryManagerModalProps> = ({ isOpen
|
|||
)}
|
||||
<button
|
||||
className="lib-uninstall-btn"
|
||||
onClick={() => handleUninstall(getInstalledName(lib))}
|
||||
onClick={() =>
|
||||
lib.custom
|
||||
? handleRemoveCustom(getInstalledName(lib))
|
||||
: handleUninstall(getInstalledName(lib))
|
||||
}
|
||||
disabled={uninstallingLib !== null}
|
||||
title={lib.custom ? 'Remove your custom upload' : 'Uninstall library'}
|
||||
>
|
||||
{uninstallingLib === getInstalledName(lib) ? 'Uninstalling...' : 'UNINSTALL'}
|
||||
{uninstallingLib === getInstalledName(lib)
|
||||
? 'Removing...'
|
||||
: lib.custom
|
||||
? 'Remove'
|
||||
: 'UNINSTALL'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<ArduinoLibrary[]> {
|
||||
|
|
@ -74,6 +76,42 @@ export async function getInstalledLibraries(): Promise<InstalledLibrary[]> {
|
|||
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<InstalledLibrary[]> {
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue