From f4bea5e3fd33dd73a940566567b9bf3b178e93af Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Tue, 11 Nov 2025 10:31:32 +1100 Subject: [PATCH] feat: enhance TagService to improve tag extraction and fallback logic; update TagEditor for category filtering and styling; add middle click zoom reset in WaveformEditorCanvas; refine app.css for layout adjustments --- src/main/services/TagService.ts | 39 +++---- src/renderer/src/App.tsx | 8 ++ src/renderer/src/components/FileList.tsx | 1 + src/renderer/src/components/TagEditor.tsx | 100 +++++++++++------- .../components/edit/WaveformEditorCanvas.tsx | 7 ++ src/renderer/src/styles/app.css | 15 ++- 6 files changed, 98 insertions(+), 72 deletions(-) diff --git a/src/main/services/TagService.ts b/src/main/services/TagService.ts index 4e2ba8b..d81a342 100644 --- a/src/main/services/TagService.ts +++ b/src/main/services/TagService.ts @@ -61,15 +61,19 @@ export class TagService { const buffer = fs.readFileSync(filePath); const wave = new WaveFile(buffer); const tags = this.extractInfoTagMap(wave); + + // Read from individual fields + const title = tags.INAM?.trim() || undefined; + const author = tags.IART?.trim() || undefined; + const copyright = tags.ICOP?.trim() || undefined; + const ratingValue = tags.IRTD ? Number.parseInt(tags.IRTD, 10) : undefined; let rating: number | undefined; if (typeof ratingValue === 'number' && Number.isFinite(ratingValue)) { rating = Math.floor(ratingValue / 2); } - - const copyright = tags.ICOP?.trim() || undefined; - // Try to read parentId from JSON comment first + // Try to read parentId from JSON comment let parentId: number | undefined; const comment = tags.ICMT?.trim(); if (comment) { @@ -79,17 +83,12 @@ export class TagService { parentId = parsed.parentId; } } catch { - // Not JSON or invalid, try fallback to IPAR - const parentRaw = tags.IPAR?.trim(); - if (parentRaw && parentRaw.length > 0) { - const parsedNum = Number.parseInt(parentRaw, 10); - if (Number.isFinite(parsedNum)) { - parentId = parsedNum; - } - } + // Not JSON, ignore } - } else { - // No comment, try fallback to IPAR + } + + // Fallback to IPAR field if no JSON parentId + if (parentId === undefined) { const parentRaw = tags.IPAR?.trim(); if (parentRaw && parentRaw.length > 0) { const parsedNum = Number.parseInt(parentRaw, 10); @@ -100,8 +99,8 @@ export class TagService { } return { - author: tags.IART?.trim() || undefined, - title: tags.INAM?.trim() || undefined, + author, + title, rating, copyright, parentId @@ -176,13 +175,7 @@ export class TagService { ? String(metadata.parentId) : null; const commentPayload = { - version: 1, - tags: tagValuesList, - categories: categoryValuesList, - parentId: metadata.parentId ?? null, - title: effectiveTitle, - author: effectiveAuthor, - rating: metadata.rating ?? null + parentId: metadata.parentId ?? null } satisfies Record; const commentText = JSON.stringify(commentPayload); @@ -206,7 +199,7 @@ export class TagService { fs.writeFileSync(filePath, updatedBuffer); console.log(`Wrote metadata to ${filePath}:`, { - tags: Array.isArray(metadata.tags) ? metadata.tags.join(', ') : '', + comment: commentText, categories: metadata.categories.join(', '), title: effectiveTitle, author: effectiveAuthor, diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 4750339..2a929a2 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -171,6 +171,14 @@ function App(): JSX.Element { return; } await libraryStore.organizeFile(selectedFile.id, metadata); + + // Scroll the file into view after organizing (filename may have changed) + setTimeout(() => { + const element = document.querySelector(`[data-file-id="${selectedFile.id}"]`); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + }, 100); }; const handleUpdateCustomName = async (customName: string | null) => { diff --git a/src/renderer/src/components/FileList.tsx b/src/renderer/src/components/FileList.tsx index f763eae..1d140bc 100644 --- a/src/renderer/src/components/FileList.tsx +++ b/src/renderer/src/components/FileList.tsx @@ -145,6 +145,7 @@ export function FileList({ files, selectedId, selectedIds, onSelect, onPlay, sea type="button" className={className} draggable={true} + data-file-id={file.id} ref={(node) => { if (node) { buttonRefs.current.set(file.id, node); diff --git a/src/renderer/src/components/TagEditor.tsx b/src/renderer/src/components/TagEditor.tsx index 36e735e..0705eed 100644 --- a/src/renderer/src/components/TagEditor.tsx +++ b/src/renderer/src/components/TagEditor.tsx @@ -171,45 +171,6 @@ export function TagEditor({ categories, availableCategories, onSave, showHeading return (
{showHeading &&

Categories

} -
- setCategoryFilter(event.target.value)} - placeholder="Filter UCS categories" - /> -
- {categoryFilter.trim().length > 0 && ( - filterSuggestions.length > 0 ? ( -
- Suggestions -
- {filterSuggestions.map((entry) => { - const selected = selectedCategories.has(entry.id); - const colorStyle = createCategoryStyleVars(categoryColorMap.get(entry.id)); - const categoryLabel = formatCategoryLabel(entry.category); - const subCategoryLabel = formatCategoryLabel(entry.subCategory); - return ( - - ); - })} -
-
- ) : ( -
No categories match this filter.
- ) - )}
{selectedCategoryRecords.length === 0 && No categories selected} @@ -235,6 +196,12 @@ export function TagEditor({ categories, availableCategories, onSave, showHeading star.style.width = 'auto'; star.style.padding = '0 4px'; } + const removeBtn = e.currentTarget.querySelector('.selected-category-chip-remove-button') as HTMLElement; + if (removeBtn) { + removeBtn.style.opacity = '1'; + removeBtn.style.width = 'auto'; + removeBtn.style.padding = '0 4px'; + } }} onMouseLeave={(e) => { const star = e.currentTarget.querySelector('.category-star-button') as HTMLElement; @@ -243,6 +210,12 @@ export function TagEditor({ categories, availableCategories, onSave, showHeading star.style.width = '0'; star.style.padding = '0'; } + const removeBtn = e.currentTarget.querySelector('.selected-category-chip-remove-button') as HTMLElement; + if (removeBtn) { + removeBtn.style.opacity = '0'; + removeBtn.style.width = '0'; + removeBtn.style.padding = '0'; + } }} title={isPrimary ? `${categoryLabel} ${subCategoryLabel} (Primary - used for organization)` : `${categoryLabel} ${subCategoryLabel}`} > @@ -286,10 +259,16 @@ export function TagEditor({ categories, availableCategories, onSave, showHeading style={{ background: 'none', border: 'none', - padding: '0 4px', + padding: '0', + margin: 0, cursor: 'pointer', fontSize: '18px', - lineHeight: 1 + lineHeight: 1, + opacity: 0, + width: '0', + overflow: 'hidden', + transition: 'opacity 0.2s ease, width 0.2s ease, padding 0.2s ease', + flexShrink: 0 }} aria-label="Remove category" > @@ -308,6 +287,45 @@ export function TagEditor({ categories, availableCategories, onSave, showHeading Clear All
+
+ setCategoryFilter(event.target.value)} + placeholder="Filter UCS categories" + /> +
+ {categoryFilter.trim().length > 0 && ( + filterSuggestions.length > 0 ? ( +
+ Suggestions +
+ {filterSuggestions.map((entry) => { + const selected = selectedCategories.has(entry.id); + const colorStyle = createCategoryStyleVars(categoryColorMap.get(entry.id)); + const categoryLabel = formatCategoryLabel(entry.category); + const subCategoryLabel = formatCategoryLabel(entry.subCategory); + return ( + + ); + })} +
+
+ ) : ( +
No categories match this filter.
+ ) + )}