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 && (
+
+ )}
+ {!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 (