feat: delete selected

This commit is contained in:
florian 2024-06-06 16:16:34 +02:00
parent 36a3b6a854
commit 50ef5fa0aa
6 changed files with 86 additions and 90 deletions

View File

@ -8,6 +8,7 @@ import {
MusicalNoteIcon,
PhotoIcon,
TrashIcon,
XMarkIcon,
} from '@heroicons/react/24/outline';
import { formatFileSize, formatDate } from '../../utils/utils';
import ImageBlobList from '../ImageBlobList/ImageBlobList';
@ -23,7 +24,7 @@ import { useBlobSelection } from './useBlobSelection';
type BlobListProps = {
blobs: BlobDescriptor[];
onDelete?: (blob: BlobDescriptor) => void;
onDelete?: (blobs: BlobDescriptor[]) => void;
title?: string;
className?: string;
};
@ -32,7 +33,7 @@ const BlobList = ({ blobs, onDelete, title, className = '' }: BlobListProps) =>
const [mode, setMode] = useState<ListMode>('list');
const { distribution } = useServerInfo();
const fileMetaEventsByHash = useFileMetaEventsByHash();
const { handleSelectBlob, selectedBlobs } = useBlobSelection(blobs);
const { handleSelectBlob, selectedBlobs, setSelectedBlobs } = useBlobSelection(blobs);
const images = useMemo(
() => blobs.filter(b => b.type?.startsWith('image/')).sort((a, b) => (a.uploaded > b.uploaded ? -1 : 1)), // descending
[blobs]
@ -66,13 +67,6 @@ const BlobList = ({ blobs, onDelete, title, className = '' }: BlobListProps) =>
<ClipboardDocumentIcon />
</a>
</span>
{onDelete && (
<span>
<a onClick={() => onDelete(blob)} className="link link-primary tooltip" data-tip="Delete this blob">
<TrashIcon />
</a>
</span>
)}
</div>
);
@ -100,7 +94,24 @@ const BlobList = ({ blobs, onDelete, title, className = '' }: BlobListProps) =>
{title && <h2>{title}</h2>}
{selectedCount > 0 && (
<div className="flex bg-base-200 rounded-box gap-1 mr-2 py-4 px-8">{selectedCount} blobs selected </div>
<div className="flex bg-base-200 rounded-box gap-2 mr-2 py-2 px-8 align-middle items-center">
{selectedCount} blobs selected
{onDelete && (
<button
className="btn btn-icon btn-primary btn-sm tooltip"
onClick={async () => {
await onDelete(blobs.filter(b => selectedBlobs[b.sha256]));
setSelectedBlobs({});
}}
data-tip="Delete the selected blobs"
>
<TrashIcon />
</button>
)}
<button className="btn btn-icon btn-sm" onClick={() => setSelectedBlobs({})}>
<XMarkIcon className="h-6 w-6 text-gray-500" />
</button>
</div>
)}
<BlobListTypeMenu
mode={mode}
@ -113,19 +124,14 @@ const BlobList = ({ blobs, onDelete, title, className = '' }: BlobListProps) =>
</div>
{mode == 'gallery' && (
<ImageBlobList
images={images}
onDelete={onDelete}
selectedBlobs={selectedBlobs}
handleSelectBlob={handleSelectBlob}
/>
<ImageBlobList images={images} selectedBlobs={selectedBlobs} handleSelectBlob={handleSelectBlob} />
)}
{mode == 'video' && <VideoBlobList videos={videos} onDelete={onDelete} />}
{mode == 'video' && <VideoBlobList videos={videos} />}
{mode == 'audio' && <AudioBlobList audioFiles={audioFiles} onDelete={onDelete} />}
{mode == 'audio' && <AudioBlobList audioFiles={audioFiles} />}
{mode == 'docs' && <DocumentBlobList docs={docs} onDelete={onDelete} />}
{mode == 'docs' && <DocumentBlobList docs={docs} />}
{mode == 'list' && (
<div className="blob-list">
@ -147,15 +153,17 @@ const BlobList = ({ blobs, onDelete, title, className = '' }: BlobListProps) =>
key={blob.sha256}
onClick={e => handleSelectBlob(blob.sha256, e)}
>
<td className="whitespace-nowrap">
<td className="whitespace-nowrap w-12">
<input
type="checkbox"
className="checkbox checkbox-primary checkbox-sm mr-2"
checked={selectedBlobs[blob.sha256]}
checked={!!selectedBlobs[blob.sha256]}
onChange={e => handleSelectBlob(blob.sha256, e)}
onClick={e => e.stopPropagation()}
/>
{getMimeTypeIcon(blob.type)}
</td>
<td className="whitespace-nowrap">
<a className="link link-primary" href={blob.url} target="_blank">
{blob.sha256.slice(0, 15)}
</a>

View File

@ -1,5 +1,5 @@
import { BlobDescriptor } from 'blossom-client-sdk';
import { useCallback, useState } from 'react';
import { useState } from 'react';
export type HandleSelectBlobType = (
sha256: string,
@ -8,10 +8,10 @@ export type HandleSelectBlobType = (
export const useBlobSelection = (blobs: BlobDescriptor[]) => {
const [selectedBlobs, setSelectedBlobs] = useState<{ [key: string]: boolean }>({});
const handleSelectBlob: HandleSelectBlobType = useCallback(
(sha256: string, event: React.MouseEvent<HTMLElement> | React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const handleSelectBlob: HandleSelectBlobType = (
sha256: string,
event: React.MouseEvent<HTMLElement> | React.ChangeEvent<HTMLInputElement>
) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isMouseEvent = (event: any): event is React.MouseEvent<HTMLTableRowElement> => {
return event.ctrlKey !== undefined || event.metaKey !== undefined;
@ -21,29 +21,38 @@ export const useBlobSelection = (blobs: BlobDescriptor[]) => {
if (event.ctrlKey || event.metaKey) {
setSelectedBlobs(prev => ({
...prev,
[sha256]: !prev[sha256]
[sha256]: !prev[sha256],
}));
} else if (event.shiftKey) {
const lastSelectedIndex = blobs.findIndex(blob => blob.sha256 === Object.keys(selectedBlobs).find(key => selectedBlobs[key]));
const lastSelectedIndex = blobs.findIndex(
blob => blob.sha256 === Object.keys(selectedBlobs).find(key => selectedBlobs[key])
);
const currentIndex = blobs.findIndex(blob => blob.sha256 === sha256);
const [start, end] = [lastSelectedIndex, currentIndex].sort((a, b) => a - b);
const newSelection = blobs.slice(start, end + 1).reduce((acc, blob) => {
const newSelection = blobs.slice(start, end + 1).reduce(
(acc, blob) => {
acc[blob.sha256] = true;
return acc;
}, {} as { [key: string]: boolean });
},
{} as { [key: string]: boolean }
);
setSelectedBlobs(prev => ({
...prev,
...newSelection
...newSelection,
}));
} else {
setSelectedBlobs({ [sha256]: true });
setSelectedBlobs(prev => ({
...prev,
[sha256]: !prev[sha256],
}));
}
} else {
setSelectedBlobs({ [sha256]: true });
setSelectedBlobs(prev => ({
...prev,
[sha256]: !prev[sha256],
}));
}
},
[blobs, selectedBlobs]
);
};
return { handleSelectBlob, selectedBlobs, setSelectedBlobs };
};

View File

@ -1,5 +1,5 @@
import { formatFileSize, formatDate } from '../../utils/utils';
import { ClipboardDocumentIcon, TrashIcon } from '@heroicons/react/24/outline';
import { ClipboardDocumentIcon } from '@heroicons/react/24/outline';
import { BlobDescriptor } from 'blossom-client-sdk';
import { Document, Page } from 'react-pdf';
@ -8,7 +8,7 @@ type DocumentBlobListProps = {
onDelete?: (blob: BlobDescriptor) => void;
};
const DocumentBlobList = ({ docs, onDelete }: DocumentBlobListProps) => (
const DocumentBlobList = ({ docs }: DocumentBlobListProps) => (
<div className="blob-list grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-2 justify-center">
{docs.map(blob => (
<div key={blob.sha256} className="p-4 rounded-lg bg-base-300 relative flex flex-col">
@ -31,13 +31,6 @@ const DocumentBlobList = ({ docs, onDelete }: DocumentBlobListProps) => (
<ClipboardDocumentIcon />
</a>
</span>
{onDelete && (
<span>
<a onClick={() => onDelete(blob)} className="link link-primary tooltip" data-tip="Delete this blob">
<TrashIcon />
</a>
</span>
)}
</div>
</div>
))}

View File

@ -1,16 +1,15 @@
import { formatFileSize, formatDate } from '../../utils/utils';
import { ClipboardDocumentIcon, TrashIcon } from '@heroicons/react/24/outline';
import { ClipboardDocumentIcon } from '@heroicons/react/24/outline';
import { BlobDescriptor } from 'blossom-client-sdk';
import { HandleSelectBlobType } from '../BlobList/useBlobSelection';
type ImageBlobListProps = {
images: BlobDescriptor[];
onDelete?: (blob: BlobDescriptor) => void;
handleSelectBlob: HandleSelectBlobType;
selectedBlobs: { [key: string]: boolean };
};
const ImageBlobList = ({ images, onDelete, handleSelectBlob, selectedBlobs }: ImageBlobListProps) => (
const ImageBlobList = ({ images, handleSelectBlob, selectedBlobs }: ImageBlobListProps) => (
<div className="blob-list grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-2 justify-center">
{images.map(blob => (
<div
@ -47,13 +46,6 @@ const ImageBlobList = ({ images, onDelete, handleSelectBlob, selectedBlobs }: Im
<ClipboardDocumentIcon />
</a>
</span>
{onDelete && (
<span>
<a onClick={() => onDelete(blob)} className="link link-primary tooltip" data-tip="Delete this blob">
<TrashIcon />
</a>
</span>
)}
</div>
</div>
))}

View File

@ -1,13 +1,12 @@
import { formatFileSize, formatDate } from '../../utils/utils';
import { ClipboardDocumentIcon, TrashIcon } from '@heroicons/react/24/outline';
import { ClipboardDocumentIcon } from '@heroicons/react/24/outline';
import { BlobDescriptor } from 'blossom-client-sdk';
type VideoBlobListProps = {
videos: BlobDescriptor[];
onDelete?: (blob: BlobDescriptor) => void;
};
const VideoBlobList = ({ videos, onDelete }: VideoBlobListProps) => (
const VideoBlobList = ({ videos }: VideoBlobListProps) => (
<div className="blob-list grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-2 justify-center">
{videos.map(blob => (
<div key={blob.sha256} className="p-4 rounded-lg bg-base-300 relative text-center">
@ -26,13 +25,6 @@ const VideoBlobList = ({ videos, onDelete }: VideoBlobListProps) => (
<ClipboardDocumentIcon />
</a>
</span>
{onDelete && (
<span>
<a onClick={() => onDelete(blob)} className="link link-primary tooltip" data-tip="Delete this blob">
<TrashIcon />
</a>
</span>
)}
</div>
</div>
))}

View File

@ -72,13 +72,15 @@ function Home() {
className="mt-4"
title={`Content on ${serverInfo[selectedServer].name}`}
blobs={selectedServerBlobs}
onDelete={blob =>
deleteBlob.mutate({
onDelete={async blobs => {
for (const blob of blobs) {
await deleteBlob.mutateAsync({
serverName: serverInfo[selectedServer].name,
serverUrl: serverInfo[selectedServer].url,
hash: blob.sha256,
})
});
}
}}
></BlobList>
)}
</>