From f280ad2ee9c84710caacf55939677d8fca8334b1 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Mon, 15 Jun 2026 23:04:02 -0400 Subject: [PATCH] feat(SearchAssembly64, SearchCSDbNG): improve null handling and enhance loading states --- src/app/components/SearchAssembly64.tsx | 11 +-- src/app/components/SearchCSDbNG.tsx | 106 +++++++++++------------- src/app/components/ui/dialog.tsx | 73 ++++++++-------- 3 files changed, 89 insertions(+), 101 deletions(-) diff --git a/src/app/components/SearchAssembly64.tsx b/src/app/components/SearchAssembly64.tsx index 2f0ab82..b0b208a 100644 --- a/src/app/components/SearchAssembly64.tsx +++ b/src/app/components/SearchAssembly64.tsx @@ -267,7 +267,7 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA const needle = filterText.trim().toLowerCase(); let list = needle ? results.filter(r => - r.name.toLowerCase().includes(needle) || + (r.name ?? '').toLowerCase().includes(needle) || (r.group?.toLowerCase().includes(needle)) || (r.handle?.toLowerCase().includes(needle)) ) @@ -276,7 +276,7 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA let cmp: number; if (sortField === 'year') cmp = (a.year ?? 0) - (b.year ?? 0); else if (sortField === 'rating') cmp = (a.siteRating ?? 0) - (b.siteRating ?? 0); - else cmp = a.name.localeCompare(b.name); + else cmp = (a.name ?? '').localeCompare(b.name ?? ''); return sortDir === 'asc' ? cmp : -cmp; }); return list; @@ -287,6 +287,7 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA // ── Item entries ───────────────────────────────────────────────────────────── const openItem = async (item: ContentItem) => { + inputRef.current?.blur(); setSelectedItem(item); setEntries(null); setLoadingEntries(true); @@ -515,11 +516,11 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA {isSearching && } {visibleResults.length}{results.length !== visibleResults.length ? ` of ${results.length}` : ''} result{visibleResults.length !== 1 ? 's' : ''}{hasMore ? '+' : ''}

- {visibleResults.map(item => { + {visibleResults.map((item, idx) => { const catLabel = categoryName[item.category] ?? `Cat ${item.category}`; return (
)} - {!loadingRelease && release?.DownloadLinks.length === 0 && ( + {!loadingRelease && release?.DownloadLinks.filter(l => isRelativeLink(l.Link)).length === 0 && (

No download links found

)} - {!loadingRelease && release && release.DownloadLinks.length > 0 && ( + {!loadingRelease && release && release.DownloadLinks.some(l => isRelativeLink(l.Link)) && (
- {release.DownloadLinks.map((link, i) => { - const url = resolveLink(link.Link); - const isMounted = mountedUrls.has(url); + {release.DownloadLinks.filter(l => isRelativeLink(l.Link)).map((link, i) => { const fname = link.Filename || basename(link.Link) || selectedRow!.name; + const localPath = joinPath(DOWNLOAD_DIR, fname); + const isMounted = mountedUrls.has(localPath); return ( -
setMountEntry({ row: selectedRow!, link })} + className={`px-4 py-3 rounded-lg border flex items-center gap-3 text-left w-full transition-colors ${isMounted ? 'border-blue-300 bg-blue-50' : 'border-neutral-200 hover:bg-blue-50 hover:border-blue-300'}`} > +
{fname}
-
- {link.Downloads > 0 ? `${link.Downloads.toLocaleString()} downloads` : link.Status} -
- - -
+ {isMounted && Mounted} + ); })}
@@ -507,7 +489,13 @@ export default function SearchCSDbNG({ config, setConfig, onClose }: SearchCSDbN
- {(() => { + {isMounting && ( +
+ +

Downloading…

+
+ )} + {!isMounting && (() => { const allDevices = Object.entries(config?.devices?.iec ?? {}); const drives = allDevices .filter(([, v]: [string, any]) => (v as any)?.type === 'drive') diff --git a/src/app/components/ui/dialog.tsx b/src/app/components/ui/dialog.tsx index e79d88e..fd794c1 100644 --- a/src/app/components/ui/dialog.tsx +++ b/src/app/components/ui/dialog.tsx @@ -30,47 +30,46 @@ function DialogClose({ return ; } -function DialogOverlay({ - className, - ...props -}: React.ComponentProps) { - return ( - , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + - ); -} - -function DialogContent({ - className, - children, - ...props -}: React.ComponentProps) { - return ( - - - - {children} - - - Close - - - - ); -} + > + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { return (