feat: Added image resizing

This commit is contained in:
florian 2024-06-30 12:27:36 +02:00
parent c2ac9a5481
commit 694cb4cc49
8 changed files with 175 additions and 51 deletions

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 { AudioBlob, ID3Tag, fetchId3Tag } from '../../utils/id3';
import { useQueries } from '@tanstack/react-query';

View File

@ -33,7 +33,12 @@ const Badge = ({ ev }: { ev: NDKEvent }) => {
relays: ev.onRelays.map(r => r.url),
} as AddressPointer);
return (
<a target="_blank" className="badge badge-primary mr-2 tooltip" href={`https://blossom.hzrd149.com/#/drive/${naddr}`} data-tip={driveIdentifier}>
<a
target="_blank"
className="badge badge-primary mr-2 tooltip"
href={`https://blossom.hzrd149.com/#/drive/${naddr}`}
data-tip={driveIdentifier}
>
🌸 drive
</a>
);

View File

@ -61,6 +61,7 @@ const BlobList = ({ blobs, onDelete, title, className = '' }: BlobListProps) =>
className="link link-primary tooltip"
data-tip="Copy link to clipboard"
onClick={e => {
e.preventDefault();
e.stopPropagation();
navigator.clipboard.writeText(blob.url);
}}

View File

@ -54,16 +54,16 @@ function Home() {
return (
<>
<ServerList
servers={Object.values(serverInfo).sort()}
selectedServer={selectedServer}
setSelectedServer={setSelectedServer}
onTransfer={() => navigate('/transfer/' + selectedServer)}
onCheck={() => navigate('/check/' + selectedServer)}
title={<>Servers</>}
manageServers={true}
withVirtualServers={true}
></ServerList>
<ServerList
servers={Object.values(serverInfo).sort()}
selectedServer={selectedServer}
setSelectedServer={setSelectedServer}
onTransfer={() => navigate('/transfer/' + selectedServer)}
onCheck={() => navigate('/check/' + selectedServer)}
title={<>Servers</>}
manageServers={true}
withVirtualServers={true}
></ServerList>
{selectedServer && serverInfo[selectedServer] && selectedServerBlobs && (
<BlobList

View File

@ -12,6 +12,7 @@ import { formatFileSize } from '../utils/utils';
import FileEventEditor, { FileEventData } from '../components/FileEventEditor/FileEventEditor';
import pLimit from 'p-limit';
import { Server, useUserServers } from '../utils/useUserServers';
import { resizeImage } from '../utils/resize';
type TransferStats = {
enabled: boolean;
@ -33,6 +34,31 @@ steps
-
*/
type ResizeOptionType = {
name: string;
format?: string;
width?: number;
height?: number;
};
const ResizeOptions: ResizeOptionType[] = [
{
name: 'Orignal Image',
width: undefined,
height: undefined,
},
{
name: 'max. 2048x2048 pixels',
width: 2048,
height: 2048,
},
{
name: 'max. 1080x1080 pixels',
width: 1080,
height: 1080,
},
];
function Upload() {
const servers = useUserServers();
const { signEventTemplate } = useNDK();
@ -48,6 +74,8 @@ function Upload() {
const [fileEventsToPublish, setFileEventsToPublish] = useState<FileEventData[]>([]);
const [uploadBusy, setUploadBusy] = useState(false);
const [imageResize, setImageResize] = useState(0);
// const [resizeImages, setResizeImages] = useState(false);
// const [publishToNostr, setPublishToNostr] = useState(false);
@ -94,15 +122,22 @@ function Upload() {
const filesToUpload: File[] = [];
for (const f of files) {
if (cleanPrivateData) {
filesToUpload.push(await removeExifData(f));
} else {
filesToUpload.push(f);
let processedFile = f;
if (processedFile.type.startsWith('image/')) {
// Do image processing according to options
if (imageResize > 0) {
const { width, height } = ResizeOptions[imageResize];
processedFile = await resizeImage(processedFile, width, height);
}
if (cleanPrivateData) {
processedFile = await removeExifData(processedFile);
}
}
filesToUpload.push(processedFile);
}
// TODO use https://github.com/davejm/client-compress
// for image resizing
const fileDimensions: { [key: string]: FileEventData } = {};
for (const file of filesToUpload) {
let data = {
@ -232,30 +267,44 @@ function Upload() {
return (
<>
<h2 className=" py-4">Upload</h2>
<button className='btn btn-primary' onClick={async () => {
const url = "https://media-server.slidestr.net/3c3f3f0b67c17953e59ebdb53b7fd83bf68b552823b927fa9718a52e12d53c0a";
const targetServer= "https://test-store.slidestr.net";
{/*
<button
className="btn btn-primary"
onClick={async () => {
const url =
'https://media-server.slidestr.net/3c3f3f0b67c17953e59ebdb53b7fd83bf68b552823b927fa9718a52e12d53c0a';
const targetServer = 'https://test-store.slidestr.net';
const headers = {
Accept: 'application/json',
'Content-Type':'application/json',
'Content-Type': 'application/json',
};
const blossomClient = new BlossomClient(targetServer, signEventTemplate);
const mirrorAuth = await blossomClient.getMirrorAuth('3c3f3f0b67c17953e59ebdb53b7fd83bf68b552823b927fa9718a52e12d53c0a', 'Upload Blob');
const mirrorAuth = await blossomClient.getMirrorAuth(
'3c3f3f0b67c17953e59ebdb53b7fd83bf68b552823b927fa9718a52e12d53c0a',
'Upload Blob'
);
const res = await axios.put<BlobDescriptor>(`${targetServer}/mirror`, { url }, {
headers: mirrorAuth ? { ...headers, authorization: BlossomClient.encodeAuthorizationHeader(mirrorAuth) } : headers,
});
const res = await axios.put<BlobDescriptor>(
`${targetServer}/mirror`,
{ url },
{
headers: mirrorAuth
? { ...headers, authorization: BlossomClient.encodeAuthorizationHeader(mirrorAuth) }
: headers,
}
);
console.log(res.status);
console.log(res.data);
}}
>
Test Mirror
</button>
*/}
}}>Test Mirror</button>
<div className=" bg-base-200 rounded-xl p-4 text-neutral-content gap-4 flex flex-col">
<input
id="browse"
@ -299,8 +348,8 @@ function Upload() {
</>
))}
</div>
<h3 className="text-lg text-neutral-content">Options</h3>
<div className="cursor-pointer grid gap-2" style={{ gridTemplateColumns: '1.5em auto' }}>
<h3 className="text-lg text-neutral-content">Image Options</h3>
<div className="cursor-pointer grid gap-2 items-center" style={{ gridTemplateColumns: '1.5em auto' }}>
<CheckBox
name="cleanData"
disabled={uploadBusy}
@ -308,20 +357,31 @@ function Upload() {
setChecked={c => setCleanPrivateData(c)}
label="Clean private data in images (EXIF)"
></CheckBox>
{/*
<CheckBox
name="resize"
checked={resizeImages}
setChecked={c => setResizeImages(c)}
label="Resize images to max. 2048 x 2048 (NOT IMPLEMENTED YET!)"
></CheckBox>
<CheckBox
name="publish"
checked={publishToNostr}
setChecked={c => setPublishToNostr(c)}
label="Publish to NOSTR (as 1063 file metadata event) (NOT IMPLEMENTED YET!)"
></CheckBox>
*/}
<input
className="checkbox checkbox-primary "
id="resizeOption"
disabled={uploadBusy}
type="checkbox"
checked={imageResize > 0}
onChange={() => setImageResize(irs => (irs > 0 ? 0 : 1))}
/>
<div>
<label htmlFor="resizeOption" className="cursor-pointer select-none">
Resize Image
</label>
<select
disabled={uploadBusy || imageResize == 0}
className="select select-bordered select-sm ml-4 w-full max-w-xs"
onChange={e => setImageResize(e.target.selectedIndex)}
value={imageResize}
>
{ResizeOptions.map((ro, i) => (
<option key={ro.name} disabled={i == 0}>
{ro.name}
</option>
))}
</select>
</div>
</div>
<div className="flex flex-row gap-2">
<button className="btn btn-primary" onClick={() => upload()} disabled={uploadBusy || files.length == 0}>
@ -347,7 +407,7 @@ function Upload() {
<>
<h2 className="py-4">Publish events</h2>
{fileEventsToPublish.map(fe => (
<FileEventEditor data={fe} />
<FileEventEditor key={fe.x} data={fe} />
))}
</>
)}

58
src/utils/resize.ts Normal file
View File

@ -0,0 +1,58 @@
async function compress(
file: File,
{ quality = 0.95, maxWidth, maxHeight }: { quality: number; maxWidth?: number; maxHeight?: number }
): Promise<File> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = async function () {
const source = {
width: img.naturalWidth,
height: img.naturalHeight,
};
const target = {
width: maxWidth || source.width,
height: maxHeight || source.height,
};
const aspectRatio = Math.min(1, target.width / source.width, target.height / source.height);
target.width = source.width * aspectRatio;
target.height = source.height * aspectRatio;
const canvas = document.createElement('canvas');
canvas.width = target.width;
canvas.height = target.height;
const context = canvas.getContext('2d') as CanvasRenderingContext2D;
context?.drawImage(img, 0, 0, target.width, target.height);
canvas.toBlob(
blob => {
if (blob == null) {
reject('Could not convert image.');
return;
}
const newFile = new File([blob], file.name, {
type: file.type,
lastModified: Date.now(),
});
resolve(newFile);
},
file.type,
quality
);
};
img.src = URL.createObjectURL(file);
});
}
export async function resizeImage(input: File, width?: number, height?: number): Promise<File> {
const result = await compress(input, {
quality: 0.95,
maxWidth: width,
maxHeight: height,
});
return result;
}

View File

@ -14,7 +14,7 @@ export interface ServerInfo extends Server {
isLoading: boolean;
isError: boolean;
blobs?: BlobDescriptor[];
};
}
type BlobDictionary = {
[key: string]: { blob: BlobDescriptor; servers: string[] };
@ -87,7 +87,7 @@ export const useServerInfo = () => {
name: 'All servers',
url: 'all',
blobs: [],
type: 'blossom'
type: 'blossom',
};
const allInfo = serversInfos.reduce(
(acc, server) => ({

View File

@ -36,7 +36,7 @@ export const useUserServers = (): Server[] => {
url: s.toLocaleLowerCase().replace(/\/$/, ''),
type: 'blossom' as ServerType,
})),
/* ...(nip96ServerListEvent?.getMatchingTags('server').map(t => t[1]) || []).map(s => ({
/* ...(nip96ServerListEvent?.getMatchingTags('server').map(t => t[1]) || []).map(s => ({
url: s.toLocaleLowerCase().replace(/\/$/, ''),
type: 'nip96' as ServerType,
})),*/
@ -48,7 +48,7 @@ export const useUserServers = (): Server[] => {
url: s.url,
}));
}, [blossomServerListEvent, nip96ServerListEvent]);
// console.log(servers);
return servers;
};