diff --git a/bun.lockb b/bun.lockb index 95d4de7..0e7cfcb 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/src/components/BlobList/BlobList.tsx b/src/components/BlobList/BlobList.tsx index 1678ed2..8d2b9af 100644 --- a/src/components/BlobList/BlobList.tsx +++ b/src/components/BlobList/BlobList.tsx @@ -14,6 +14,7 @@ import { useEffect, useMemo, useState } from 'react'; import { Document, Page } from 'react-pdf'; import * as id3 from 'id3js'; import { ID3Tag, ID3TagV2 } from 'id3js/lib/id3Tag'; +import { useQueries } from '@tanstack/react-query'; type ListMode = 'gallery' | 'list' | 'audio' | 'video' | 'docs'; @@ -27,7 +28,6 @@ type AudioBlob = BlobDescriptor & { id3?: ID3Tag; imageData?: string }; const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { const [mode, setMode] = useState('list'); - const [audioFiles, setAudioFiles] = useState([]); const images = useMemo( () => blobs.filter(b => b.type?.startsWith('image/')).sort((a, b) => (a.created > b.created ? -1 : 1)), // descending @@ -39,36 +39,40 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { [blobs] ); - const fetchId3Tags = async (audioFiles: AudioBlob[]) => { - const id3Tags = await Promise.all(audioFiles.map(af => id3.fromUrl(af.url))); + const fetchId3Tag = async (blob: BlobDescriptor) => { + const id3Tag = await id3.fromUrl(blob.url).catch(e => console.warn(e)); - const filesWithTags = audioFiles.map((af, i) => ({ ...af, id3: id3Tags[i] || undefined })); - - for (const af of filesWithTags) { - if (af.id3 && af.id3.kind == 'v2') { - const id3v2 = af.id3 as ID3TagV2; - if (id3v2.images[0].data) { - const base64data = btoa( - new Uint8Array(id3v2.images[0].data).reduce(function (data, byte) { - return data + String.fromCharCode(byte); - }, '') - ); - af.imageData = `data:${id3v2.images[0].type};base64,${base64data}`; - } + if (id3Tag && id3Tag.kind == 'v2') { + const id3v2 = id3Tag as ID3TagV2; + if (id3v2.images[0].data) { + const base64data = btoa( + new Uint8Array(id3v2.images[0].data).reduce(function (data, byte) { + return data + String.fromCharCode(byte); + }, '') + ); + const imageData = `data:${id3v2.images[0].type};base64,${base64data}`; + return { ...blob, id3: id3Tag, imageData } as AudioBlob; } } - - setAudioFiles(filesWithTags); - return; + return { ...blob, id3: id3Tag } as AudioBlob; }; - useEffect(() => { - const audioFiles = blobs.filter(b => b.type?.startsWith('audio/')).sort((a, b) => (a.created > b.created ? -1 : 1)); + const audioFiles = useMemo( + () => blobs.filter(b => b.type?.startsWith('audio/')).sort((a, b) => (a.created > b.created ? -1 : 1)), + [blobs] + ); - setAudioFiles(audioFiles); - - fetchId3Tags(audioFiles); - }, [blobs]); + const audioFilesWithId3 = useQueries({ + queries: audioFiles.map(af => ({ + queryKey: ['id3', af.sha256], + queryFn: async () => { + return await fetchId3Tag(af); + }, + enabled: mode == 'audio' && !!audioFiles && audioFiles.length > 0, + staleTime: 1000 * 60 * 5, + cacheTime: 1000 * 60 * 5, + })), + }); const docs = useMemo( () => blobs.filter(b => b.type?.startsWith('application/pdf')).sort((a, b) => (a.created > b.created ? -1 : 1)), // descending @@ -160,7 +164,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { {mode == 'gallery' && (
{images.map(blob => ( -
+
{ {mode == 'video' && (
{videos.map(blob => ( -
+
{formatFileSize(blob.size)} @@ -202,42 +210,52 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { )} {mode == 'audio' && ( -
- {audioFiles.map(blob => ( -
- {blob.id3 && ( -
- {blob.imageData && ( - +
+ {audioFilesWithId3.map( + blob => + blob.isSuccess && ( +
+ {blob.data.id3 && ( +
+ {blob.data.imageData && } + +
+ {blob.data.id3.title && {blob.data.id3.title}} + {blob.data.id3.artist && {blob.data.id3.artist}} + {blob.data.id3.album && ( + + {blob.data.id3.album} {blob.data.id3.year ? `(${blob.data.id3.year})` : ''} + + )} +
+
)} -
- {blob.id3.title && {blob.id3.title}} - {blob.id3.artist && {blob.id3.artist}} - {blob.id3.album && ( - - {blob.id3.album} {blob.id3.year ? `(${blob.id3.year})` : ''} - - )} -
-
- )} - + -
- {formatFileSize(blob.size)} - {formatDate(blob.created)} -
- -
- ))} +
+ {formatFileSize(blob.data.size)} + {formatDate(blob.data.created)} +
+ +
+ ) + )}
)} {mode == 'docs' && (
{docs.map(blob => ( -
+
{ + set(idbValidKey, client); + }, + restoreClient: async () => { + return await get(idbValidKey); + }, + removeClient: async () => { + await del(idbValidKey); + }, + } as Persister; +} +*/ const router = createBrowserRouter( createRoutesFromElements( @@ -30,13 +47,36 @@ const router = createBrowserRouter( ) ); +// nst persister = createIDBPersister(); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + gcTime: 1000 * 60 * 60 * 24, // 24 hours + }, + }, +}); + ReactDOM.createRoot(document.getElementById('root')!).render( + {/* + true, //query.state.status === 'success', + }, + hydrateOptions: { + shouldhHhydrateQuery: () => true, + }, + }} + client={queryClient} + >*/} - {/* */} + ); diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4209243..0a1fab9 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -12,6 +12,8 @@ import { useNavigate } from 'react-router-dom'; // TODOs /* +- display flle progress for transfer +- stop/pause transfer - multi threaded sync - Add server and pulbish list event - upload to single/multi servers