mirror of
https://github.com/litruv/AudioSort.git
synced 2026-07-24 02:36:01 +10:00
Refactor metadata handling: remove copyright fields from various services and components
This commit is contained in:
@@ -29,7 +29,7 @@ export class LibraryService {
|
||||
private readonly metadataFailures = new Set<string>();
|
||||
private readonly checksumFailures = new Set<string>();
|
||||
private readonly organization: OrganizationService;
|
||||
private metadataSuggestionCache: { authors: Set<string>; copyrights: Set<string> } | null = null;
|
||||
private metadataSuggestionCache: { authors: Set<string> } | null = null;
|
||||
private readonly waveformPreviewCache = new Map<number, { modifiedAt: number; pointCount: number; samples: number[]; rms: number }>();
|
||||
|
||||
public constructor(
|
||||
@@ -378,7 +378,6 @@ export class LibraryService {
|
||||
return this.organizeFile(payload.fileId, {
|
||||
customName: updated.customName ?? undefined,
|
||||
author: metadataSnapshot.author?.trim() || undefined,
|
||||
copyright: metadataSnapshot.copyright?.trim() || undefined,
|
||||
rating: metadataSnapshot.rating ?? undefined
|
||||
});
|
||||
}
|
||||
@@ -435,7 +434,7 @@ export class LibraryService {
|
||||
*/
|
||||
public async organizeFile(
|
||||
fileId: number,
|
||||
metadata: { customName?: string | null; author?: string | null; copyright?: string | null; rating?: number }
|
||||
metadata: { customName?: string | null; author?: string | null; rating?: number }
|
||||
): Promise<AudioFileSummary> {
|
||||
const record = this.database.getFileById(fileId);
|
||||
const category = this.organization.getPrimaryCategory(record);
|
||||
@@ -503,9 +502,7 @@ export class LibraryService {
|
||||
|
||||
// Merge with new metadata (only update fields that are provided)
|
||||
const requestedAuthor = this.normaliseMetadataInput(metadata.author);
|
||||
const requestedCopyright = this.normaliseMetadataInput(metadata.copyright);
|
||||
const mergedAuthor = requestedAuthor !== undefined ? requestedAuthor : existing.author ?? null;
|
||||
const mergedCopyright = requestedCopyright !== undefined ? requestedCopyright : existing.copyright ?? null;
|
||||
const mergedRating = metadata.rating !== undefined ? metadata.rating : existing.rating;
|
||||
|
||||
this.tagService.writeMetadataOnly(updatedRecord.absolutePath, {
|
||||
@@ -513,16 +510,12 @@ export class LibraryService {
|
||||
categories: updatedRecord.categories,
|
||||
title: effectiveCustomName,
|
||||
author: mergedAuthor,
|
||||
copyright: mergedCopyright,
|
||||
rating: mergedRating
|
||||
});
|
||||
|
||||
// Update suggestions cache for any metadata that was provided
|
||||
if (typeof mergedAuthor === 'string' && mergedAuthor.length > 0) {
|
||||
this.updateMetadataSuggestionsCache(mergedAuthor, undefined);
|
||||
}
|
||||
if (typeof mergedCopyright === 'string' && mergedCopyright.length > 0) {
|
||||
this.updateMetadataSuggestionsCache(undefined, mergedCopyright);
|
||||
this.updateMetadataSuggestionsCache(mergedAuthor);
|
||||
}
|
||||
|
||||
this.resetMetadataSuggestionsCache();
|
||||
@@ -602,9 +595,7 @@ export class LibraryService {
|
||||
|
||||
// Merge with new metadata (only update fields that are provided)
|
||||
const movedAuthor = this.normaliseMetadataInput(metadata.author);
|
||||
const movedCopyright = this.normaliseMetadataInput(metadata.copyright);
|
||||
const mergedAuthor = movedAuthor !== undefined ? movedAuthor : existing.author ?? null;
|
||||
const mergedCopyright = movedCopyright !== undefined ? movedCopyright : existing.copyright ?? null;
|
||||
const mergedRating = metadata.rating !== undefined ? metadata.rating : existing.rating;
|
||||
|
||||
// Write tags, categories, and additional metadata to the organized file
|
||||
@@ -613,16 +604,12 @@ export class LibraryService {
|
||||
categories: updated.categories,
|
||||
title: effectiveCustomName,
|
||||
author: mergedAuthor,
|
||||
copyright: mergedCopyright,
|
||||
rating: mergedRating
|
||||
});
|
||||
|
||||
// Update suggestions cache for any metadata that was provided
|
||||
if (typeof mergedAuthor === 'string' && mergedAuthor.length > 0) {
|
||||
this.updateMetadataSuggestionsCache(mergedAuthor, undefined);
|
||||
}
|
||||
if (typeof mergedCopyright === 'string' && mergedCopyright.length > 0) {
|
||||
this.updateMetadataSuggestionsCache(undefined, mergedCopyright);
|
||||
this.updateMetadataSuggestionsCache(mergedAuthor);
|
||||
}
|
||||
|
||||
this.resetMetadataSuggestionsCache();
|
||||
@@ -666,20 +653,20 @@ export class LibraryService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads embedded metadata from a WAV file and returns author, copyright, title, and rating.
|
||||
* Reads embedded metadata from a WAV file and returns author, title, and rating.
|
||||
*/
|
||||
public async readFileMetadata(fileId: number): Promise<{ author?: string; copyright?: string; title?: string; rating?: number }> {
|
||||
public async readFileMetadata(fileId: number): Promise<{ author?: string; title?: string; rating?: number }> {
|
||||
const record = this.database.getFileById(fileId);
|
||||
const metadata = this.tagService.readMetadata(record.absolutePath);
|
||||
this.updateMetadataSuggestionsCache(metadata.author, metadata.copyright);
|
||||
this.updateMetadataSuggestionsCache(metadata.author);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates metadata (author, copyright, rating) without organizing the file.
|
||||
* Updates metadata (author, rating) without organizing the file.
|
||||
* Only writes to the WAV INFO chunk, doesn't move or rename the file.
|
||||
*/
|
||||
public async updateFileMetadata(fileId: number, metadata: { author?: string | null; copyright?: string | null; rating?: number }): Promise<void> {
|
||||
public async updateFileMetadata(fileId: number, metadata: { author?: string | null; rating?: number }): Promise<void> {
|
||||
const record = this.database.getFileById(fileId);
|
||||
|
||||
// Read existing metadata from the WAV file
|
||||
@@ -687,13 +674,11 @@ export class LibraryService {
|
||||
|
||||
// Merge with new metadata (only update fields that are provided)
|
||||
const requestedAuthor = this.normaliseMetadataInput(metadata.author);
|
||||
const requestedCopyright = this.normaliseMetadataInput(metadata.copyright);
|
||||
const mergedMetadata = {
|
||||
tags: record.tags,
|
||||
categories: record.categories,
|
||||
title: record.customName ?? existing.title,
|
||||
author: requestedAuthor !== undefined ? requestedAuthor : existing.author ?? null,
|
||||
copyright: requestedCopyright !== undefined ? requestedCopyright : existing.copyright ?? null,
|
||||
rating: metadata.rating !== undefined ? metadata.rating : existing.rating
|
||||
};
|
||||
|
||||
@@ -702,10 +687,7 @@ export class LibraryService {
|
||||
|
||||
// Update suggestions cache
|
||||
if (typeof mergedMetadata.author === 'string' && mergedMetadata.author.length > 0) {
|
||||
this.updateMetadataSuggestionsCache(mergedMetadata.author, undefined);
|
||||
}
|
||||
if (typeof mergedMetadata.copyright === 'string' && mergedMetadata.copyright.length > 0) {
|
||||
this.updateMetadataSuggestionsCache(undefined, mergedMetadata.copyright);
|
||||
this.updateMetadataSuggestionsCache(mergedMetadata.author);
|
||||
}
|
||||
|
||||
this.waveformPreviewCache.delete(fileId);
|
||||
@@ -715,10 +697,9 @@ export class LibraryService {
|
||||
/**
|
||||
* Returns distinct metadata suggestions aggregated from the library.
|
||||
*/
|
||||
public async listMetadataSuggestions(): Promise<{ authors: string[]; copyrights: string[] }> {
|
||||
public async listMetadataSuggestions(): Promise<{ authors: string[] }> {
|
||||
if (!this.metadataSuggestionCache) {
|
||||
const authors = new Set<string>();
|
||||
const copyrights = new Set<string>();
|
||||
const files = this.database.listFiles();
|
||||
|
||||
for (const file of files) {
|
||||
@@ -727,9 +708,6 @@ export class LibraryService {
|
||||
if (metadata.author) {
|
||||
this.addSuggestionValue(authors, metadata.author);
|
||||
}
|
||||
if (metadata.copyright) {
|
||||
this.addSuggestionValue(copyrights, metadata.copyright);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!this.metadataFailures.has(file.absolutePath)) {
|
||||
this.metadataFailures.add(file.absolutePath);
|
||||
@@ -739,12 +717,11 @@ export class LibraryService {
|
||||
}
|
||||
}
|
||||
|
||||
this.metadataSuggestionCache = { authors, copyrights };
|
||||
this.metadataSuggestionCache = { authors };
|
||||
}
|
||||
|
||||
return {
|
||||
authors: Array.from(this.metadataSuggestionCache.authors).sort((a, b) => a.localeCompare(b)),
|
||||
copyrights: Array.from(this.metadataSuggestionCache.copyrights).sort((a, b) => a.localeCompare(b))
|
||||
authors: Array.from(this.metadataSuggestionCache.authors).sort((a, b) => a.localeCompare(b))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -769,16 +746,13 @@ export class LibraryService {
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
private updateMetadataSuggestionsCache(author?: string | null, copyright?: string | null): void {
|
||||
private updateMetadataSuggestionsCache(author?: string | null): void {
|
||||
if (!this.metadataSuggestionCache) {
|
||||
return;
|
||||
}
|
||||
if (author) {
|
||||
this.addSuggestionValue(this.metadataSuggestionCache.authors, author);
|
||||
}
|
||||
if (copyright) {
|
||||
this.addSuggestionValue(this.metadataSuggestionCache.copyrights, copyright);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,6 @@ export class TagService {
|
||||
*/
|
||||
public readMetadata(filePath: string): {
|
||||
author?: string;
|
||||
copyright?: string;
|
||||
title?: string;
|
||||
rating?: number;
|
||||
} {
|
||||
@@ -58,7 +57,6 @@ export class TagService {
|
||||
|
||||
return {
|
||||
author: tags.IART?.trim() || undefined,
|
||||
copyright: tags.ICOP?.trim() || undefined,
|
||||
title: tags.INAM?.trim() || undefined,
|
||||
rating
|
||||
};
|
||||
@@ -79,7 +77,6 @@ export class TagService {
|
||||
categories: string[];
|
||||
title?: string | null;
|
||||
author?: string | null;
|
||||
copyright?: string | null;
|
||||
rating?: number;
|
||||
}
|
||||
): void {
|
||||
@@ -96,7 +93,6 @@ export class TagService {
|
||||
categories: string[];
|
||||
title?: string | null;
|
||||
author?: string | null;
|
||||
copyright?: string | null;
|
||||
rating?: number;
|
||||
}
|
||||
): void {
|
||||
@@ -123,7 +119,6 @@ export class TagService {
|
||||
IGNR: null,
|
||||
INAM: metadata.title?.trim()?.length ? metadata.title.trim() : null,
|
||||
IART: metadata.author?.trim()?.length ? metadata.author.trim() : null,
|
||||
ICOP: metadata.copyright?.trim()?.length ? metadata.copyright.trim() : null,
|
||||
IRTD: metadata.rating && metadata.rating > 0 ? String(metadata.rating * 2) : null,
|
||||
ISFT: 'AudioSort'
|
||||
};
|
||||
@@ -138,7 +133,6 @@ export class TagService {
|
||||
categories: metadata.categories.join(', '),
|
||||
title: metadata.title,
|
||||
author: metadata.author,
|
||||
copyright: metadata.copyright,
|
||||
rating: metadata.rating
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -10,8 +10,8 @@ export interface FileDetailPanelProps {
|
||||
categories: CategoryRecord[];
|
||||
onRename(newName: string): Promise<void>;
|
||||
onMove(targetRelativeDirectory: string): Promise<void>;
|
||||
/** Organizes a file with optional metadata fields (customName, author, copyright, rating 1-5). */
|
||||
onOrganize(metadata: { customName?: string | null; author?: string | null; copyright?: string | null; rating?: number }): Promise<void>;
|
||||
/** Organizes a file with optional metadata fields (customName, author, rating 1-5). */
|
||||
onOrganize(metadata: { customName?: string | null; author?: string | null; rating?: number }): Promise<void>;
|
||||
onUpdateTags(data: { tags: string[]; categories: string[] }): Promise<void>;
|
||||
onUpdateCustomName(customName: string | null): Promise<void>;
|
||||
metadataSuggestionsVersion: number;
|
||||
@@ -29,9 +29,8 @@ export function FileDetailPanel({ file, categories, onRename, onMove, onOrganize
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [isTagSectionExpanded, setIsTagSectionExpanded] = useState(true);
|
||||
const [suggestions, setSuggestions] = useState<{ authors: string[] }>({ authors: [] });
|
||||
const initialMetadataRef = useRef<{ author: string; copyright: string; rating: number; customName: string }>({
|
||||
const initialMetadataRef = useRef<{ author: string; rating: number; customName: string }>({
|
||||
author: '',
|
||||
copyright: '',
|
||||
rating: 0,
|
||||
customName: ''
|
||||
});
|
||||
@@ -94,7 +93,7 @@ export function FileDetailPanel({ file, categories, onRename, onMove, onOrganize
|
||||
setAuthorDraft('');
|
||||
setRatingDraft(0);
|
||||
setIsEditingCustomName(false);
|
||||
initialMetadataRef.current = { author: '', copyright: '', rating: 0, customName: '' };
|
||||
initialMetadataRef.current = { author: '', rating: 0, customName: '' };
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,7 +101,7 @@ export function FileDetailPanel({ file, categories, onRename, onMove, onOrganize
|
||||
setCustomNameDraft(file.customName ?? '');
|
||||
setIsEditingCustomName(false);
|
||||
const baseCustomName = (file.customName ?? '').trim();
|
||||
initialMetadataRef.current = { author: '', copyright: '', rating: 0, customName: baseCustomName };
|
||||
initialMetadataRef.current = { author: '', rating: 0, customName: baseCustomName };
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
@@ -118,7 +117,6 @@ export function FileDetailPanel({ file, categories, onRename, onMove, onOrganize
|
||||
setRatingDraft(ratingValue);
|
||||
initialMetadataRef.current = {
|
||||
author: authorValue,
|
||||
copyright: '',
|
||||
rating: ratingValue,
|
||||
customName: customNameValue
|
||||
};
|
||||
@@ -129,7 +127,7 @@ export function FileDetailPanel({ file, categories, onRename, onMove, onOrganize
|
||||
console.error('Failed to read file metadata:', error);
|
||||
setAuthorDraft('');
|
||||
setRatingDraft(0);
|
||||
initialMetadataRef.current = { author: '', copyright: '', rating: 0, customName: baseCustomName };
|
||||
initialMetadataRef.current = { author: '', rating: 0, customName: baseCustomName };
|
||||
}
|
||||
})();
|
||||
}, [appendSuggestion, file?.id]);
|
||||
@@ -206,7 +204,6 @@ export function FileDetailPanel({ file, categories, onRename, onMove, onOrganize
|
||||
const trimmedCustomName = (nextCustomNameRaw ?? '').toString().trim();
|
||||
const comparisonState = {
|
||||
author: trimmedAuthor,
|
||||
copyright: '',
|
||||
rating: nextRatingValue,
|
||||
customName: trimmedCustomName
|
||||
};
|
||||
@@ -215,7 +212,6 @@ export function FileDetailPanel({ file, categories, onRename, onMove, onOrganize
|
||||
if (
|
||||
previous &&
|
||||
previous.author === comparisonState.author &&
|
||||
previous.copyright === comparisonState.copyright &&
|
||||
previous.rating === comparisonState.rating &&
|
||||
previous.customName === comparisonState.customName
|
||||
) {
|
||||
|
||||
@@ -8,8 +8,8 @@ export interface MultiFileEditorProps {
|
||||
categories: CategoryRecord[];
|
||||
onUpdateTags(fileId: number, data: { tags: string[]; categories: string[] }): Promise<void>;
|
||||
onUpdateCustomName(fileId: number, customName: string | null): Promise<void>;
|
||||
onOrganize(fileId: number, metadata: { customName?: string | null; author?: string | null; copyright?: string | null; rating?: number }): Promise<void>;
|
||||
onUpdateMetadata(fileId: number, metadata: { author?: string | null; copyright?: string | null; rating?: number }): Promise<void>;
|
||||
onOrganize(fileId: number, metadata: { customName?: string | null; author?: string | null; rating?: number }): Promise<void>;
|
||||
onUpdateMetadata(fileId: number, metadata: { author?: string | null; rating?: number }): Promise<void>;
|
||||
metadataSuggestionsVersion: number;
|
||||
}
|
||||
|
||||
@@ -19,9 +19,8 @@ export interface MultiFileEditorProps {
|
||||
export function MultiFileEditor({ files, categories, onUpdateTags, onUpdateCustomName, onOrganize, onUpdateMetadata, metadataSuggestionsVersion }: MultiFileEditorProps): JSX.Element {
|
||||
const [sharedCustomName, setSharedCustomName] = useState('');
|
||||
const [sharedAuthor, setSharedAuthor] = useState('');
|
||||
const [sharedCopyright, setSharedCopyright] = useState('');
|
||||
const [sharedRating, setSharedRating] = useState(0);
|
||||
const [suggestions, setSuggestions] = useState<{ authors: string[]; copyrights: string[] }>({ authors: [], copyrights: [] });
|
||||
const [suggestions, setSuggestions] = useState<{ authors: string[] }>({ authors: [] });
|
||||
const [isTagSectionExpanded, setIsTagSectionExpanded] = useState(true);
|
||||
|
||||
// Compute aggregated tags and categories for TagEditor
|
||||
@@ -53,7 +52,7 @@ export function MultiFileEditor({ files, categories, onUpdateTags, onUpdateCusto
|
||||
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
type SuggestionKey = 'authors' | 'copyrights';
|
||||
type SuggestionKey = 'authors';
|
||||
|
||||
const appendSuggestion = useCallback((value: string, key: SuggestionKey) => {
|
||||
const trimmed = value.trim();
|
||||
@@ -84,21 +83,9 @@ export function MultiFileEditor({ files, categories, onUpdateTags, onUpdateCusto
|
||||
return base.filter((entry) => entry.toLowerCase().includes(query)).slice(0, 5);
|
||||
}, [sharedAuthor, suggestions.authors]);
|
||||
|
||||
const filteredCopyrightSuggestions = useMemo(() => {
|
||||
if (suggestions.copyrights.length === 0) {
|
||||
return [] as string[];
|
||||
}
|
||||
const query = sharedCopyright.trim().toLowerCase();
|
||||
const base = suggestions.copyrights.filter((entry) => entry.toLowerCase() !== query);
|
||||
if (query.length === 0) {
|
||||
return base.slice(0, 5);
|
||||
}
|
||||
return base.filter((entry) => entry.toLowerCase().includes(query)).slice(0, 5);
|
||||
}, [sharedCopyright, suggestions.copyrights]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setSuggestions({ authors: [], copyrights: [] });
|
||||
setSuggestions({ authors: [] });
|
||||
void (async () => {
|
||||
try {
|
||||
const result = await window.api.listMetadataSuggestions();
|
||||
@@ -106,8 +93,7 @@ export function MultiFileEditor({ files, categories, onUpdateTags, onUpdateCusto
|
||||
return;
|
||||
}
|
||||
setSuggestions({
|
||||
authors: result.authors,
|
||||
copyrights: result.copyrights
|
||||
authors: result.authors
|
||||
});
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
@@ -188,37 +174,6 @@ export function MultiFileEditor({ files, categories, onUpdateTags, onUpdateCusto
|
||||
}
|
||||
};
|
||||
|
||||
const handleSharedCopyrightBlur = async () => {
|
||||
const normalized = sharedCopyright.trim();
|
||||
|
||||
if (normalized.length === 0) return;
|
||||
|
||||
try {
|
||||
let failureCount = 0;
|
||||
for (const file of files) {
|
||||
try {
|
||||
if (file.categories.length > 0) {
|
||||
await onOrganize(file.id, { copyright: normalized });
|
||||
} else {
|
||||
await onUpdateMetadata(file.id, { copyright: normalized });
|
||||
}
|
||||
} catch (error) {
|
||||
failureCount += 1;
|
||||
console.error(`Failed to update copyright for file ${file.id} (${file.fileName}):`, error);
|
||||
}
|
||||
}
|
||||
if (failureCount > 0) {
|
||||
console.error(`${failureCount} file(s) failed to update copyright`);
|
||||
}
|
||||
|
||||
if (normalized.length > 0) {
|
||||
appendSuggestion(normalized, 'copyrights');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update copyright:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSharedRatingChange = async (rating: number) => {
|
||||
setSharedRating(rating);
|
||||
|
||||
@@ -325,39 +280,6 @@ export function MultiFileEditor({ files, categories, onUpdateTags, onUpdateCusto
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
<label>
|
||||
<span>Copyright</span>
|
||||
<input
|
||||
type="text"
|
||||
value={sharedCopyright}
|
||||
onChange={(e) => setSharedCopyright(e.target.value)}
|
||||
onBlur={handleSharedCopyrightBlur}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
placeholder="Copyright information"
|
||||
/>
|
||||
{filteredCopyrightSuggestions.length > 0 && (
|
||||
<div className="metadata-suggestion-list" role="listbox" aria-label="Copyright suggestions">
|
||||
{filteredCopyrightSuggestions.map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
type="button"
|
||||
className="metadata-suggestion"
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => {
|
||||
setSharedCopyright(option);
|
||||
void handleSharedCopyrightBlur();
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
<label>
|
||||
<span>Rating</span>
|
||||
<div className="star-rating">
|
||||
|
||||
@@ -8,11 +8,10 @@ import {
|
||||
type SyntheticEvent
|
||||
} from 'react';
|
||||
|
||||
type SuggestionType = 'author' | 'copyright';
|
||||
type SuggestionType = 'author';
|
||||
|
||||
interface MetadataSuggestionPayload {
|
||||
authors: string[];
|
||||
copyrights: string[];
|
||||
}
|
||||
|
||||
export interface SearchBarProps {
|
||||
@@ -30,7 +29,7 @@ export interface SearchBarProps {
|
||||
export function SearchBar({ value, onChange, onRescan, onFindDuplicates, onOpenSettings, metadataSuggestionsVersion }: SearchBarProps): JSX.Element {
|
||||
const [draft, setDraft] = useState(value);
|
||||
const [caretIndex, setCaretIndex] = useState<number | null>(null);
|
||||
const [suggestions, setSuggestions] = useState<MetadataSuggestionPayload>({ authors: [], copyrights: [] });
|
||||
const [suggestions, setSuggestions] = useState<MetadataSuggestionPayload>({ authors: [] });
|
||||
const [loadingSuggestions, setLoadingSuggestions] = useState(false);
|
||||
const [rescanBusy, setRescanBusy] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -44,8 +43,7 @@ export function SearchBar({ value, onChange, onRescan, onFindDuplicates, onOpenS
|
||||
try {
|
||||
const result = await window.api.listMetadataSuggestions();
|
||||
setSuggestions({
|
||||
authors: result.authors,
|
||||
copyrights: result.copyrights
|
||||
authors: result.authors
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load search suggestions', error);
|
||||
@@ -55,7 +53,7 @@ export function SearchBar({ value, onChange, onRescan, onFindDuplicates, onOpenS
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setSuggestions({ authors: [], copyrights: [] });
|
||||
setSuggestions({ authors: [] });
|
||||
void refreshSuggestions();
|
||||
}, [refreshSuggestions, metadataSuggestionsVersion]);
|
||||
|
||||
@@ -65,7 +63,7 @@ export function SearchBar({ value, onChange, onRescan, onFindDuplicates, onOpenS
|
||||
}, [caretIndex, draft]);
|
||||
|
||||
const activeToken = useMemo(() => {
|
||||
const match = analysisTarget.match(/(author|copyright):([^\s]*)$/i);
|
||||
const match = analysisTarget.match(/(author):([^\s]*)$/i);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
@@ -80,7 +78,7 @@ export function SearchBar({ value, onChange, onRescan, onFindDuplicates, onOpenS
|
||||
if (!activeToken) {
|
||||
return [] as string[];
|
||||
}
|
||||
const pool = activeToken.type === 'author' ? suggestions.authors : suggestions.copyrights;
|
||||
const pool = suggestions.authors;
|
||||
const trimmedTerm = activeToken.term.trim();
|
||||
if (trimmedTerm.length === 0) {
|
||||
return pool.slice(0, 6);
|
||||
|
||||
@@ -297,7 +297,7 @@ export class LibraryStore extends EventTarget {
|
||||
/**
|
||||
* Automatically organizes a file based on its categories.
|
||||
*/
|
||||
public async organizeFile(fileId: number, metadata: { customName?: string | null; author?: string | null; copyright?: string | null; rating?: number }): Promise<AudioFileSummary> {
|
||||
public async organizeFile(fileId: number, metadata: { customName?: string | null; author?: string | null; rating?: number }): Promise<AudioFileSummary> {
|
||||
const currentSelection = this.snapshot.selectedFileId;
|
||||
const currentFocused = this.snapshot.focusedFile;
|
||||
const updated = await window.api.organizeFile(fileId, metadata);
|
||||
@@ -335,9 +335,9 @@ export class LibraryStore extends EventTarget {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates metadata (author, copyright, rating) for a file without organizing it.
|
||||
* Updates metadata (author, rating) for a file without organizing it.
|
||||
*/
|
||||
public async updateFileMetadata(fileId: number, metadata: { author?: string | null; copyright?: string | null; rating?: number }): Promise<void> {
|
||||
public async updateFileMetadata(fileId: number, metadata: { author?: string | null; rating?: number }): Promise<void> {
|
||||
await window.api.updateFileMetadata(fileId, metadata);
|
||||
|
||||
// Refresh the file list to get updated metadata
|
||||
|
||||
@@ -45,7 +45,7 @@ export interface RendererApi {
|
||||
/** Moves a file to another subdirectory under the library root. */
|
||||
moveFile(fileId: number, targetRelativeDirectory: string): Promise<import('./models').AudioFileSummary>;
|
||||
/** Automatically organizes a file based on its categories. */
|
||||
organizeFile(fileId: number, metadata: { customName?: string | null; author?: string | null; copyright?: string | null; rating?: number }): Promise<import('./models').AudioFileSummary>;
|
||||
organizeFile(fileId: number, metadata: { customName?: string | null; author?: string | null; rating?: number }): Promise<import('./models').AudioFileSummary>;
|
||||
/** Updates the custom name for a file. */
|
||||
updateCustomName(fileId: number, customName: string | null): Promise<import('./models').AudioFileSummary>;
|
||||
/** Opens the file's containing folder in the system file explorer. */
|
||||
@@ -63,11 +63,11 @@ export interface RendererApi {
|
||||
/** Runs fuzzy search returning ranked results. */
|
||||
search(query: string): Promise<import('./models').AudioFileSummary[]>;
|
||||
/** Reads embedded metadata from a WAV file. */
|
||||
readFileMetadata(fileId: number): Promise<{ author?: string; copyright?: string; title?: string; rating?: number }>;
|
||||
readFileMetadata(fileId: number): Promise<{ author?: string; title?: string; rating?: number }>;
|
||||
/** Lists distinct metadata values gathered from the library for quick suggestions. */
|
||||
listMetadataSuggestions(): Promise<{ authors: string[]; copyrights: string[] }>;
|
||||
/** Updates metadata (author, copyright, rating) without organizing the file. */
|
||||
updateFileMetadata(fileId: number, metadata: { author?: string | null; copyright?: string | null; rating?: number }): Promise<void>;
|
||||
listMetadataSuggestions(): Promise<{ authors: string[] }>;
|
||||
/** Updates metadata (author, rating) without organizing the file. */
|
||||
updateFileMetadata(fileId: number, metadata: { author?: string | null; rating?: number }): Promise<void>;
|
||||
/** Listens for menu actions from the main process. Returns a cleanup function. */
|
||||
onMenuAction(channel: string, callback: () => void): () => void;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node10",
|
||||
"moduleResolution": "Bundler",
|
||||
"outDir": "dist/main",
|
||||
"rootDir": "src",
|
||||
"types": ["node"],
|
||||
|
||||
Reference in New Issue
Block a user