From e5c2a7fca57cbb06194b3ba7c571356a3192d270 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Mon, 15 Jun 2026 00:47:22 -0400 Subject: [PATCH] feat: add new search panel components and update existing ones with headers and close buttons --- public/assets/favicon.a64.png | Bin 0 -> 2061 bytes public/assets/favicon.cbm.png | Bin 0 -> 701 bytes src/app/App.tsx | 38 ++++++++--------- src/app/components/SearchAssembly64.tsx | 14 ++++++- src/app/components/SearchCSDbNG.tsx | 16 +++++-- src/app/components/SearchCommoServe.tsx | 14 ++++++- src/app/components/SearchLocal.tsx | 40 ++++++++---------- src/app/components/SearchPane.tsx | 53 ++++++++++-------------- src/vite-env.d.ts | 1 + tsconfig.json | 15 +++++++ 10 files changed, 110 insertions(+), 81 deletions(-) create mode 100644 public/assets/favicon.a64.png create mode 100644 public/assets/favicon.cbm.png create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json diff --git a/public/assets/favicon.a64.png b/public/assets/favicon.a64.png new file mode 100644 index 0000000000000000000000000000000000000000..cddd3bc19bbf8ecee365088a5aa413d847ac7191 GIT binary patch literal 2061 zcmV+o2=e!dP)MvagdpG?#Tyu?d1phh1g z2Gm63B_`Du0}m3xpdrX5Kr2u{PTJC*)9!V~|1!I03rLI$!_Mx^{{Q!X-#`D%LBZ@x zV>3U0yy5Zldj_898n5*GN8`>)nD`JI4b}qCUu$rd1)Jf6!9L%yWd`5LvjxR(Qc%r? zwYw(A_I^0H?zWH6zK1Q#9jdbP-J5nlvv@=By4rXEprVx33M`0I!&8i4;8*UC1BAEZ z`iA=SA^wvYYim7c+OB%-MfLXCE5Vt}0q8^fj;xu#V%I%>P|-Y6GRCf*XE82)wrvH} z?7kbH1;}zsOp*nQ20uUT-LoDfCJ2CMN9^)w#fF!4N6oV*a3c41w5A+w&W8OLHr%*z zfQXI6q*>P8v*N-v*Sy6HL~oB|X7eG<6Wrt+1V;{zPV0a{+n6auC(eu;D>*IR$(^jv zpVMdh)?MkTt!saOPUG!;opc8XB5W+F1weY9nqhzhc)7;lO}rug3L ztT@1EgOW1LihRkX%+SFeIQW!5S9d*@=t)HxT-G_mXa9xsU{5^C#tF!&uN{SCbQ<*P zOJQu}2s6<$&MJpAN4WmZ{Xe>sM)M{3-amfvQkIP!)is#4-ju{!FZwX9HD2V~L zQjE(bFNr$+uoZbR)9*CpWh7b#6c}=^r4#_oletoN6dTkKr364EBw#dGu^nUz--S3? zDPb21KNhA<>ic1o>aa||%XX3=agHpxGQ98(VTEEYb%W#}wxPGHHbY z%}Lr`5!^Rc`xH?~JQef{LIOa>$$)m{idZ4J2ta0~jFBM%r5pna`&C!H#0~+F9GF$m z$S#KFhZ*XMtz2+`9n(^=94To^e-d;aIt^17rLp2b6S$cK7Tz`hO(RKbt;+!`g&gw8 zU63S}FyM^l1&0A(h%q`QAeusDs(~3uO)iZv6p}QfQ*g!59WXqymnF!fB^f0L)U2`$ zY!{FLOrX#?Eu*6)MaEG*l8PgWm;q!4pHr(v7JQ%Lav2=D1$OToK}UWO4CPMZKo@nO zDCw4^^-2HGv^NiMbv?5JAkFIl-XnY;u{07nT}Spbpl{VxFuLO)PSQ~9UeK0@Kr67v z?Hg5P1wdr-8sN5G>bvAgNq;Q>Vg^8)NgDuLnvRRCV+=V1MgoYO1jgk;o`^Msr9+%S zMhRuU%;+AYwIs8gYy={~76OfvQ2|zNSRkSM>^?a7-X4}G|0Z#)iCqCBfdC{_;faA6 zBZ)FYbQzaa*iW#4QM7r)5#4TF;)@@RjCBPh*Aql|IZUtd}r;p=80tNfVj=(iT z%b?jxAV&R~cn)ZxbLx*DZo<;FcS3wo&FSBkkMtJ1P_RwU#Fj=nOjIO_bIy|h#{L`w zRjV?cWRy0hk!_=J@dr16?(USr1QN-0L-)e)@F>pliUd?a0Ef3WnCh5Uk<;R8UQIs=R5Y=$IC})@sJ^3@2d+YG+vM-?83b6D# z0M2p52NIz?)f&7pAHKR3S43}D37`@goMV!V0+`d&vCH2-zGIcPr@yb5KX+_$WEZ8A za(Z3|Y<%Yzm_N`1=RA3d7*~?b)bCg@54Nq`1QYuv@LU*hw37G=Z29MpyQs)z)rr+S z^w7;4%;eNz0bui^cS$d3>qgk(sR+84oC3`QlRR%;e>WW3atMO%fS2B^4dH~Bf(tG@ z&%F8B6&`JAI;(`whgoWbGSR4@50vX`=~I%`YcM|AL@G7UZ46I>hpU2~Qx8{x=byB- zk}SRYrX??;C9n8t_UL`P*9?zrzFRB5_;pk+9{DZ_@E-y^$rVPid3O1A>+X7B;RC=2 zz4oQ=U-rj}W;1$a;^=-ki925k{-@7852)~=vv)Q00000NkvXXu0mjfHrT!( literal 0 HcmV?d00001 diff --git a/public/assets/favicon.cbm.png b/public/assets/favicon.cbm.png new file mode 100644 index 0000000000000000000000000000000000000000..12ae0641dd6cd6a441798cb28eab98db417b13fa GIT binary patch literal 701 zcmV;u0z&9{mOo*Xpxy6AS;f`ELMmtv(I6H8$CVC~7f} zaA_jV`FRC)VtI+VZ}CoJ#KkO4UN;Bx)mx(32-P zhV30<&tONECZEa}bOgw&+Z#+Rh&zluS(&CSzJce6Bk)@AdH*xG!l2z>>?x4Ue96`Qj(`PLk$ zU|PY1m*X%Q!u3ClkKkmhc@v#I<_0%McrJdbqb5H35otdW6BaR_`){Ef^Z^ ztwt5xz(?(?;cD!|!NBAn@jf7K9kMcq(Q)j`(&S4ZNRXNUF;a~-yGXBa2$!=o8GFn8 zCt8SYxR9mEy;1;qlHGao5RK+A&4#4UkAl{prOBO8fYw5E6f1>wMG@xapTE*rPnaJL z8#Hb>6Q-#3Axo1F8prda{{oOhyv0(S|1SVJ#HTDxHe)TGPdGpe$Tcj$dX0kBspB0% jsiw8#?YLF{;(Goa5Mit<)O-ku00000NkvXXu0mjfkFZI+ literal 0 HcmV?d00001 diff --git a/src/app/App.tsx b/src/app/App.tsx index 187c8ad..72fccd9 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,5 +1,6 @@ import { lazy, Suspense, useEffect, useState } from 'react'; import { Cpu, Wifi, Network, HardDrive, Activity, Search, Wrench, User, FileText, AppWindow, Folder, Edit, Eye, Database, Upload, Download, Code2, LayoutList, Image, ChevronLeft, Loader2, Terminal, Link, Printer, Maximize2, Minimize2 } from 'lucide-react'; +import { AnimatePresence } from 'motion/react'; import { Toaster, toast } from 'sonner'; import StatusPage from './components/StatusPage'; import DevicesPage from './components/DevicesPage'; @@ -54,8 +55,7 @@ type AppId = export default function App() { const [currentPage, setCurrentPage] = useState('status'); const { config, setConfig, saveStatus, pendingCount, flushNow, reload } = useSettings(); - const [showSearch, setShowSearch] = useState(false); - const [searchInitialTab, setSearchInitialTab] = useState<0 | 1 | 2 | 3 | undefined>(undefined); + const [searchPanel, setSearchPanel] = useState<'local' | 'assembly64' | 'commoserve' | 'csdb' | 'last' | null>(null); const [devicesOpenId, setDevicesOpenId] = useState(null); const [isFullscreen, setIsFullscreen] = useState(false); const [fileManagerInitialPath, setFileManagerInitialPath] = useState(undefined); @@ -118,9 +118,9 @@ export default function App() {

Management

} label="Media Manager" onClick={() => { setFileManagerInitialPath(undefined); setCurrentPage('file-manager'); }} /> - } label="Assembly64" onClick={() => { setSearchInitialTab(1); setShowSearch(true); }} /> - } label="CommoServe" onClick={() => { setSearchInitialTab(2); setShowSearch(true); }} /> - } label="CSDb" onClick={() => { setSearchInitialTab(3); setShowSearch(true); }} /> + } label="Assembly64" onClick={() => setSearchPanel('assembly64')} /> + } label="CommoServe" onClick={() => setSearchPanel('commoserve')} /> + } label="CSDb" onClick={() => setSearchPanel('csdb')} /> } label="Print Manager" onClick={() => setCurrentPage('print-manager')} /> } label="Serial Console" onClick={() => setCurrentPage('serial-console')} /> } label="Short Codes" onClick={() => setCurrentPage('serial-console')} /> @@ -262,7 +262,7 @@ function AppPage({ title, onBack }: { title: string; onBack: () => void }) { {isFullscreen ? : }
- {showSearch && ( - setShowSearch(false)} - onOpenFolder={(path: string) => { - setFileManagerInitialPath(path); - setFileManagerReturnPage('status'); - setShowSearch(false); - setCurrentPage('file-manager'); - }} - /> - )} + + {searchPanel && ( + setSearchPanel(null)} + onOpenFolder={(path: string) => { setSearchPanel(null); setFileManagerInitialPath(path); setFileManagerReturnPage('status'); setCurrentPage('file-manager'); }} + /> + )} + ); diff --git a/src/app/components/SearchAssembly64.tsx b/src/app/components/SearchAssembly64.tsx index c1ece59..c5e2ab9 100644 --- a/src/app/components/SearchAssembly64.tsx +++ b/src/app/components/SearchAssembly64.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { Search, Loader2, HardDrive, Download, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle } from 'lucide-react'; +import { Search, Loader2, HardDrive, Download, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle, X } from 'lucide-react'; import { toast } from 'sonner'; import { humanFileSize, basename, joinPath, putFileContents } from '../webdav'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog'; @@ -153,7 +153,7 @@ const AQL_TERMS = [ // ─── Component ──────────────────────────────────────────────────────────────── -export default function SearchAssembly64({ config, setConfig, onClose: _onClose }: SearchAssembly64Props) { +export default function SearchAssembly64({ config, setConfig, onClose }: SearchAssembly64Props) { const scrollRef = useRef(null); const inputRef = useRef(null); const [showAqlHelp, setShowAqlHelp] = useState(false); @@ -322,6 +322,16 @@ export default function SearchAssembly64({ config, setConfig, onClose: _onClose return ( <>
+ {/* Panel header */} +
+
+ + Assembly64 +
+ +
{/* Header */}
{/* Search input */} diff --git a/src/app/components/SearchCSDbNG.tsx b/src/app/components/SearchCSDbNG.tsx index 3720071..4c2a815 100644 --- a/src/app/components/SearchCSDbNG.tsx +++ b/src/app/components/SearchCSDbNG.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { Search, Loader2, HardDrive, Download, ChevronRight } from 'lucide-react'; +import { Search, Loader2, HardDrive, Download, ChevronRight, X, Archive } from 'lucide-react'; import { toast } from 'sonner'; import { basename, joinPath, putFileContents } from '../webdav'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog'; @@ -68,7 +68,7 @@ function typeLabel(tags: string): string { // ─── Component ──────────────────────────────────────────────────────────────── -export default function SearchCSDbNG({ config, setConfig, onClose: _onClose }: SearchCSDbNGProps) { +export default function SearchCSDbNG({ config, setConfig, onClose }: SearchCSDbNGProps) { const scrollRef = useRef(null); const [query, setQuery] = useState(() => _store.query); @@ -201,6 +201,16 @@ export default function SearchCSDbNG({ config, setConfig, onClose: _onClose }: S return ( <>
+ {/* Panel header */} +
+
+
+ +
{/* Header */}
@@ -211,7 +221,7 @@ export default function SearchCSDbNG({ config, setConfig, onClose: _onClose }: S value={query} onChange={e => setQuery(e.target.value)} onKeyDown={e => e.key === 'Enter' && !isSearching && doSearch(query)} - placeholder="Search CSDb…" + placeholder="Search CSDb-ng…" className="w-full pl-9 pr-3 py-2.5 bg-neutral-100 border-0 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-colors" disabled={isSearching} /> diff --git a/src/app/components/SearchCommoServe.tsx b/src/app/components/SearchCommoServe.tsx index 1f2eed1..ac9532a 100644 --- a/src/app/components/SearchCommoServe.tsx +++ b/src/app/components/SearchCommoServe.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { Search, Loader2, HardDrive, Download, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle } from 'lucide-react'; +import { Search, Loader2, HardDrive, Download, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle, X } from 'lucide-react'; import { toast } from 'sonner'; import { humanFileSize, basename, joinPath, putFileContents } from '../webdav'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog'; @@ -145,7 +145,7 @@ const AQL_TERMS = [ // ─── Component ──────────────────────────────────────────────────────────────── -export default function SearchCommoServe({ config, setConfig, onClose: _onClose }: SearchCommoServeProps) { +export default function SearchCommoServe({ config, setConfig, onClose }: SearchCommoServeProps) { const scrollRef = useRef(null); const inputRef = useRef(null); const [showAqlHelp, setShowAqlHelp] = useState(false); @@ -307,6 +307,16 @@ export default function SearchCommoServe({ config, setConfig, onClose: _onClose return ( <>
+ {/* Panel header */} +
+
+ + CommoServe +
+ +
{/* Header */}
diff --git a/src/app/components/SearchLocal.tsx b/src/app/components/SearchLocal.tsx index 594f51c..11c7a07 100644 --- a/src/app/components/SearchLocal.tsx +++ b/src/app/components/SearchLocal.tsx @@ -339,32 +339,23 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } return ( <>
- {/* Header */} -
-
+ {/* Panel header */} +
+
+ + Local +
+
{dbPhase === 'ready' && !busy && ( - )} - {hasSearched && ( - )} +
+
- {/* Search input */} + {/* Search input */} +
@@ -439,7 +435,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } {/* Body */}
{ _store.scrollTop = (e.currentTarget as HTMLDivElement).scrollTop; }} > {isScanning && scanPhase && ( @@ -482,7 +478,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } selected={mountedPaths.has(result.path)} nameSlot={ <> -
+
{result.name} {result.system && {result.system}} diff --git a/src/app/components/SearchPane.tsx b/src/app/components/SearchPane.tsx index 79e2ac5..80a95dd 100644 --- a/src/app/components/SearchPane.tsx +++ b/src/app/components/SearchPane.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import { X, Loader2, Database } from 'lucide-react'; +import { Loader2, Database } from 'lucide-react'; import { motion } from 'motion/react'; import SearchLocal from './SearchLocal'; import SearchAssembly64 from './SearchAssembly64'; @@ -21,15 +21,15 @@ interface SearchPaneProps { onOpenFolder: (path: string) => void; } -const TABS = ['Local', 'Assembly64', 'CommoServe', 'CSDb'] as const; - let _lastTab: 0 | 1 | 2 | 3 = (() => { const v = parseInt(localStorage.getItem('search.tab') ?? '0', 10); return (v >= 0 && v <= 3 ? v : 0) as 0 | 1 | 2 | 3; })(); +const PANEL_LABELS = ['Local', 'Assembly64', 'CommoServe', 'CSDb-ng'] as const; + export default function SearchPane({ config, setConfig, initialTab, onClose, onOpenFolder }: SearchPaneProps) { - const panelRef = useRef(null); + const panelRef = useRef(null); const [activeTab, setActiveTab] = useState<0 | 1 | 2 | 3>(initialTab ?? _lastTab); // Subscribe to the locate-database load pipeline so the user can see the @@ -61,9 +61,7 @@ export default function SearchPane({ config, setConfig, initialTab, onClose, onO }, []); // eslint-disable-line react-hooks/exhaustive-deps const scrollToTab = (idx: 0 | 1 | 2 | 3) => { - const el = panelRef.current; - if (!el) return; - el.scrollTo({ left: idx * el.clientWidth, behavior: 'smooth' }); + panelRef.current?.scrollTo({ left: idx * (panelRef.current.clientWidth), behavior: 'smooth' }); }; const handleScroll = () => { @@ -93,31 +91,6 @@ export default function SearchPane({ config, setConfig, initialTab, onClose, onO transition={{ type: 'spring', damping: 28, stiffness: 280 }} className="fixed inset-0 bg-white/80 backdrop-blur-md flex flex-col overflow-hidden" > - {/* Tab bar */} -
- {TABS.map((label, i) => ( - - ))} -
- -
- {/* Locate-database load bar. Visible only while a transfer is in flight; collapses to zero height otherwise so it doesn't take up space when there's nothing to show. */} @@ -200,6 +173,22 @@ export default function SearchPane({ config, setConfig, initialTab, onClose, onO />
+ + {/* Swipe indicator dots */} +
+ {([0, 1, 2, 3] as const).map(i => ( +
); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7d473f0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "skipLibCheck": true, + "noEmit": true, + "resolveJsonModule": true, + "isolatedModules": true, + "allowImportingTsExtensions": true + }, + "include": ["src"] +}