diff --git a/bun.lockb b/bun.lockb index 7d6963f..f812d57 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 9e2b91f..e159d92 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,14 @@ "@nostr-dev-kit/ndk-cache-dexie": "^2.2.8", "@tanstack/react-query": "^5.28.6", "@tanstack/react-query-devtools": "^5.28.6", + "add": "^2.0.6", "axios": "^1.6.8", "blossom-client-sdk": "^0.4.0", "dayjs": "^1.11.10", "nostr-tools": "^2.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-pdf": "^7.7.1", "react-router-dom": "^6.22.3" }, "devDependencies": { diff --git a/src/components/BlobList/BlobList.css b/src/components/BlobList/BlobList.css index 5929f84..5704da1 100644 --- a/src/components/BlobList/BlobList.css +++ b/src/components/BlobList/BlobList.css @@ -32,7 +32,7 @@ } .blog-list-header button { - @apply bg-neutral-800 hover:bg-neutral-700 p-2 ml-2 my-2 rounded-lg; + @apply bg-neutral-800 hover:bg-neutral-700 p-2 ml-2 my-2 text-white rounded-lg disabled:text-neutral-700 disabled:bg-neutral-900; } .blog-list-header button.selected { @@ -40,5 +40,5 @@ } .blog-list-header svg { - @apply w-6 text-white opacity-80 hover:opacity-100; + @apply w-6 opacity-80 hover:opacity-100; } diff --git a/src/components/BlobList/BlobList.tsx b/src/components/BlobList/BlobList.tsx index 47a5558..7b64bb4 100644 --- a/src/components/BlobList/BlobList.tsx +++ b/src/components/BlobList/BlobList.tsx @@ -1,10 +1,19 @@ -import { ClipboardDocumentIcon, DocumentIcon, ListBulletIcon, PhotoIcon, TrashIcon } from '@heroicons/react/24/outline'; +import { + ClipboardDocumentIcon, + DocumentIcon, + FilmIcon, + ListBulletIcon, + MusicalNoteIcon, + PhotoIcon, + TrashIcon, +} from '@heroicons/react/24/outline'; import { BlobDescriptor } from 'blossom-client-sdk'; import { formatDate, formatFileSize } from '../../utils'; import './BlobList.css'; -import { useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; +import { Document, Page } from 'react-pdf'; -type ListMode = 'gallery' | 'list'; +type ListMode = 'gallery' | 'list' | 'audio' | 'video' | 'docs'; type BlobListProps = { blobs: BlobDescriptor[]; @@ -15,48 +24,189 @@ type BlobListProps = { const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { const [mode, setMode] = useState('list'); + const images = useMemo( + () => blobs.filter(b => b.type?.startsWith('image/')).sort((a, b) => (a.created > b.created ? -1 : 1)), // descending + [blobs] + ); + + const videos = useMemo( + () => blobs.filter(b => b.type?.startsWith('video/')).sort((a, b) => (a.created > b.created ? -1 : 1)), // descending + [blobs] + ); + + const audio = useMemo( + () => blobs.filter(b => b.type?.startsWith('audio/')).sort((a, b) => (a.created > b.created ? -1 : 1)), // descending + [blobs] + ); + + const docs = useMemo( + () => blobs.filter(b => b.type?.startsWith('application/pdf')).sort((a, b) => (a.created > b.created ? -1 : 1)), // descending + [blobs] + ); + + useEffect(() => { + switch (mode) { + case 'video': + if (videos.length == 0) setMode('list'); + break; + case 'audio': + if (audio.length == 0) setMode('list'); + break; + case 'gallery': + if (images.length == 0) setMode('list'); + break; + case 'docs': + if (docs.length == 0) setMode('list'); + break; + } + }, [videos, images, audio, mode, docs]); + + const Actions = ({ blob, className }: { blob: BlobDescriptor; className?: string }) => ( +
+ + { + navigator.clipboard.writeText(blob.url); + }} + > + + + + {onDelete && ( + + onDelete(blob)} className=" cursor-pointer"> + + + + )} +
+ ); + return ( <>
{title &&

{title}

}
- - + + +
{mode == 'gallery' && ( -
- {blobs - .filter(b => b.type?.startsWith('image/')) - .sort((a, b) => (a.created > b.created ? -1 : 1)) // descending - .map(blob => ( -
- -
-
-
- {formatFileSize(blob.size)} - {formatDate(blob.created)} -
+
+ {images.map(blob => ( +
+ +
+
+
+ {formatFileSize(blob.size)} + {formatDate(blob.created)}
- ))} + +
+ ))} +
+ )} + + {mode == 'video' && ( +
+ {videos.map(blob => ( +
+ +
+ {formatFileSize(blob.size)} + {formatDate(blob.created)} +
+ +
+ ))} +
+ )} + + {mode == 'audio' && ( +
+ {audio.map(blob => ( +
+ +
+ {formatFileSize(blob.size)} + {formatDate(blob.created)} +
+ +
+ ))} +
+ )} + + {mode == 'docs' && ( +
+ {docs.map(blob => ( +
+ + + + + +
+ {formatFileSize(blob.size)} + {formatDate(blob.created)} +
+ +
+ ))}
)} @@ -75,28 +225,13 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { {formatFileSize(blob.size)} {blob.type && `${blob.type}`} {formatDate(blob.created)} -
- - {navigator.clipboard.writeText(blob.url)}}> - - - - {onDelete && ( - - - onDelete(blob)}> - - - )} -
- +
))}
)} ); - }; export default BlobList; diff --git a/src/main.tsx b/src/main.tsx index ba2e335..d6519bf 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -13,13 +13,20 @@ import Check from './pages/Check.tsx'; const queryClient = new QueryClient(); +import { pdfjs } from 'react-pdf'; + +pdfjs.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.js', + import.meta.url, +).toString(); + const router = createBrowserRouter( createRoutesFromElements( }> } /> } /> - } /> - } /> + } /> + } /> ) ); diff --git a/src/pages/Check.tsx b/src/pages/Check.tsx index 615bcab..c0ba2c4 100644 --- a/src/pages/Check.tsx +++ b/src/pages/Check.tsx @@ -16,7 +16,6 @@ function Check() { servers={Object.values(serverInfo).filter(s => s.name == source)} onCancel={() => navigate('/')} > - ); } diff --git a/src/pages/Transfer.tsx b/src/pages/Transfer.tsx index c9b76fa..239ab05 100644 --- a/src/pages/Transfer.tsx +++ b/src/pages/Transfer.tsx @@ -29,7 +29,7 @@ type TransferStatus = { export const Transfer = () => { const { source: transferSource } = useParams(); const navigate = useNavigate(); - const {serverInfo} = useServerInfo(); + const { serverInfo } = useServerInfo(); const [transferTarget, setTransferTarget] = useState(); const { signEventTemplate } = useNDK(); const queryClient = useQueryClient(); diff --git a/src/pages/Upload.tsx b/src/pages/Upload.tsx index 134c09c..1d97d27 100644 --- a/src/pages/Upload.tsx +++ b/src/pages/Upload.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, DragEvent, useEffect, useState } from 'react'; +import { ChangeEvent, DragEvent, useEffect, useMemo, useState } from 'react'; import { useServers } from '../utils/useServers'; import { BlobDescriptor, BlossomClient, SignedEvent } from 'blossom-client-sdk'; import { useNDK } from '../ndk'; @@ -9,6 +9,7 @@ import ProgressBar from '../components/ProgressBar/ProgressBar'; import { removeExifData } from '../exif'; import CheckBox from '../components/CheckBox/CheckBox'; import axios, { AxiosProgressEvent } from 'axios'; +import { formatFileSize } from '../utils'; type TransferStats = { enabled: boolean; @@ -129,6 +130,8 @@ function Upload() { } }; + const sizeOfFilesToUpload = useMemo(() => files.reduce((acc, file) => (acc += file.size), 0), [files]); + return ( <>

Upload

@@ -190,7 +193,7 @@ function Upload() { onClick={() => upload()} disabled={files.length == 0} > - Upload{files.length > 0 ? (files.length == 1 ? ` 1 file` : ` ${files.length} files`) : ''} + Upload{files.length > 0 ? (files.length == 1 ? ` 1 file` : ` ${files.length} files` ) : ''} / {formatFileSize(sizeOfFilesToUpload)}