From ddbf0d2df24a1fc27c22417f5fcbc3caaa3488ad Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Sun, 14 Jun 2026 03:21:41 -0400 Subject: [PATCH] feat(SearchAssembly64): add AQL search help dialog and terms reference --- AGENTS.md | 24 +++++++++- src/app/components/SearchAssembly64.tsx | 64 ++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 52d0f48..1f5534d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,20 @@ src/ NetworkPage.tsx # Network settings, WiFi scan/connect (via WS) OtherPage.tsx # Misc settings ToolsPage.tsx # Tools - SearchOverlay.tsx + SearchPane.tsx # Swipeable search shell: spring slide-up, tab bar (Local | Assembly64), + # X close button, horizontal snap-x scroll container; initialTab prop + # (0=Local, 1=Assembly64); onScroll updates active tab indicator + SearchLocal.tsx # Local file search panel (panel inside SearchPane, no fixed positioning); + # TOSEC/No-Intro tag parsing (PAL/NTSC, system, ISO 639-1 language); + # wildcard search (* → %, ? → _); MediaEntry rows with badges + path; + # faceted filter chips; module-level _store persists state across unmounts; + # actions dialog: "Mount on virtual drive" + "Open containing folder" + SearchAssembly64.tsx # Assembly64 Leet API browser panel (panel inside SearchPane); + # fetches /search/categories + /search/aql/presets on mount; + # AQL search with pagination; category filter chips; ContentItem result rows; + # item tap → /search/entries → file list with Download + Mount actions; + # Download saves to /sd/downloads/ via putFileContents; + # client-id: meatloaf-config header; module-level _store WiFiScanOverlay.tsx # WiFi scan results; Connect sends "connect []" via WS MediaManager.tsx # WebDAV file browser: file icons by type, kebab actions, # Configure Folder (.config editor), base_url mount logic, @@ -72,6 +85,8 @@ src/ figma/ # Figma-generated components ui/ lazy-loader.tsx # Animated progress bar for Suspense fallbacks (staged steps) + marquee-text.tsx # — ping-pong scroll when text overflows; per-instance + # @keyframes injected into via ResizeObserver; module-level _seq confirm-dialog.tsx # … other shadcn/Radix UI wrappers vendor/ @@ -122,12 +137,14 @@ Header: fullscreen toggle, search, apps grid, profile button (→ ProfilePage). Grid of app cards grouped by category, each navigates to a stub `AppPage` unless implemented: -- **Management**: Media Manager, Print Manager, **Serial Console** (implemented), Short Codes +- **Management**: Media Manager, **Assembly64** (implemented — opens SearchPane at Assembly64 tab), Print Manager, **Serial Console** (implemented), Short Codes - **Disk**: RAM/ROM Explorer, BAM Editor, Directory Editor, Sector Editor, Disk Visualizer, Dump/Write Disk Image - **Cartridge**: PRG to CRT, Magic Desk Cart Builder, Easy Flash Cart Builder - **Development**: Basic Editor, Assembler, Sprite Editor, Character Set Editor, Petscii Editor - **Display**: Idle Animation, Loading Animation, **Reality Override** (implemented), **Override Admin** (implemented) +Header search icon opens SearchPane at tab 0 (Local). "Assembly64" AppCard opens SearchPane at tab 1. + ## Work to Date (chronological) 1. **Initial commit** — project scaffolded with basic pages @@ -189,6 +206,8 @@ Grid of app cards grouped by category, each navigates to a stub `AppPage` unless 57. **Toast visibility improvements** — `` in `App.tsx` gains `richColors` (vivid green/red/amber/blue for success/error/warning/loading), `closeButton` (explicit ✕), `duration={5000}` (up from 4 s default), and `toastOptions` with `fontSize: 0.9rem`, `fontWeight: 500`, `padding: 14px 16px` 58. **MediaManager: Duplicate action** — `duplicateEntry()` generates unique `"name copy.ext"` / `"name copy 2.ext"` etc. via sequential `fileExists` checks, then calls `copyPath`; button appears in the file Actions Dialog between Download and Rename; reloads current directory on success 59. **`.vms` Virtual Media Stack format** — `.vms` files are like `.lst` but each line is `path,name` (comma-separated); name after comma becomes the `MediaSetEntry.name` display label; `PLAYLIST_EXTS = new Set(['lst', 'vms'])` exported from `MediaEntry.tsx` with `Layers` icon (indigo); `MediaManager` and `DeviceDetailOverlay` both parse `.vms` into `MediaSetEntry[]`; `mediaSetEntryUrl()` used for `fileExists` checks and `dev.url` +60. **MarqueeText component** — `ui/marquee-text.tsx`: extracted from inline MediaManager code; module-level `_seq` counter generates unique animation IDs; per-instance `@keyframes` rule injected into `` and cleaned up on unmount; `ResizeObserver` re-measures on resize; ping-pong (alternate) scroll with `ease-in-out` and 0.8 s delay; used by MediaManager, SearchLocal, SearchAssembly64 +61. **SearchLocal + SearchAssembly64 + SearchPane** — `SearchOverlay.tsx` split into three components: `SearchPane.tsx` (swipeable shell: `fixed inset-0 bg-white/80 backdrop-blur-md`, spring slide-up via `motion/react`, tab bar with underline indicator + X button, horizontal `snap-x snap-mandatory` scroll container, `onScroll` updates active tab); `SearchLocal.tsx` (local file search panel: TOSEC/No-Intro tag parsing for system/video/language facet chips, wildcard search via `toSqlLike()` in `locate-db.ts` converting `*`→`%` and `?`→`_`, `MediaEntry` rows with badges + path, module-level `_store` persists results/scroll/filters across unmounts, actions dialog with MarqueeText + "Mount on virtual drive" + "Open containing folder", mounted-path highlight via `base_url + url` resolution); `SearchAssembly64.tsx` (Assembly64 Leet API: AQL search via `GET /search/aql/{offset}/{limit}`, categories + presets on mount, category filter chips, paginated ContentItem results, item tap fetches file entries, Download saves to `/sd/downloads/` via `putFileContents`, Mount opens device picker dialog, `client-id: meatloaf-config` header, module-level `_store`); header search icon → tab 0, Apps "Assembly64" card → tab 1 ## Known Issues / Open Work @@ -273,6 +292,7 @@ All WebDAV I/O goes through `src/app/webdav.ts`: | `createFolder(path)` | MKCOL | | `deletePath(path)` | DELETE | | `movePath(from, to)` | MOVE | +| `copyPath(from, to)` | COPY | | `fileExists(path)` | HEAD → `boolean` | | `humanFileSize(bytes)` | Formatting helper | | `normalizePath` / `splitPath` / `joinPath` / `basename` | Path utilities | diff --git a/src/app/components/SearchAssembly64.tsx b/src/app/components/SearchAssembly64.tsx index 0de5ebe..aec668c 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, Trophy, Calendar, Users, RefreshCw } from 'lucide-react'; +import { Search, Loader2, HardDrive, Download, ChevronRight, Trophy, Calendar, Users, RefreshCw, HelpCircle } from 'lucide-react'; import { toast } from 'sonner'; import { humanFileSize, basename, joinPath, putFileContents } from '../webdav'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog'; @@ -105,10 +105,28 @@ function RatingStars({ value, max = 10 }: { value?: number; max?: number }) { ); } +// ─── AQL reference ─────────────────────────────────────────────────────────── + +const AQL_TERMS = [ + { term: 'name:', label: 'Name', example: 'name:manic*', description: 'Title of the release. Supports wildcards (*).' }, + { term: 'group:', label: 'Group', example: 'group:triad', description: 'Group or organization name.' }, + { term: 'handle:', label: 'Handle', example: 'handle:jco', description: 'Author or creator handle.' }, + { term: 'year:', label: 'Year', example: 'year:1983', description: 'Release year.' }, + { term: 'event:', label: 'Event', example: 'event:assembly', description: 'Party or event name.' }, + { term: 'country:', label: 'Country', example: 'country:SE', description: 'Country code (ISO 3166-1 alpha-2).' }, + { term: 'category:', label: 'Category', example: 'category:1', description: 'Category ID number.' }, + { term: 'compo:', label: 'Compo', example: 'compo:demo', description: 'Competition or compo type.' }, + { term: 'place:', label: 'Place', example: 'place:1', description: 'Placement in competition (1 = winner).' }, + { term: 'rating:', label: 'Rating', example: 'rating:9', description: 'Internal rating value.' }, + { term: 'siteRating:', label: 'Site Rating', example: 'siteRating:8', description: 'Site (user) rating value.' }, +] as const; + // ─── Component ──────────────────────────────────────────────────────────────── export default function SearchAssembly64({ config, setConfig, onClose: _onClose }: SearchAssembly64Props) { const scrollRef = useRef(null); + const inputRef = useRef(null); + const [showAqlHelp, setShowAqlHelp] = useState(false); const [query, setQuery] = useState(() => _store.query); const [results, setResults] = useState(() => _store.results); @@ -264,14 +282,23 @@ export default function SearchAssembly64({ config, setConfig, onClose: _onClose
setQuery(e.target.value)} onKeyDown={e => e.key === 'Enter' && !isSearching && handleSearch()} placeholder="name:manic* group:ultimate year:1983…" - 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" + className="w-full pl-9 pr-9 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} /> +
+ ))} + + + ); }