diff --git a/package-lock.json b/package-lock.json index 592db421..cfa255b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@heroicons/react": "^2.0.17", "@tanstack/react-query": "^5.17.8", "axios": "^1.6.5", + "browser-image-compression": "^2.0.2", "date-fns": "^3.3.1", "leaflet": "^1.9.4", "leaflet.locatecontrol": "^0.79.0", @@ -3460,6 +3461,15 @@ "node": ">=8" } }, + "node_modules/browser-image-compression": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/browser-image-compression/-/browser-image-compression-2.0.2.tgz", + "integrity": "sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==", + "license": "MIT", + "dependencies": { + "uzip": "0.20201231.0" + } + }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -12443,6 +12453,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uzip": { + "version": "0.20201231.0", + "resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz", + "integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng==", + "license": "MIT" + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/package.json b/package.json index cd196fa3..4367cc72 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@heroicons/react": "^2.0.17", "@tanstack/react-query": "^5.17.8", "axios": "^1.6.5", + "browser-image-compression": "^2.0.2", "date-fns": "^3.3.1", "leaflet": "^1.9.4", "leaflet.locatecontrol": "^0.79.0", diff --git a/src/Components/Profile/Subcomponents/GalleryForm.tsx b/src/Components/Profile/Subcomponents/GalleryForm.tsx index 576c9639..ad5a3ae8 100644 --- a/src/Components/Profile/Subcomponents/GalleryForm.tsx +++ b/src/Components/Profile/Subcomponents/GalleryForm.tsx @@ -1,3 +1,6 @@ +import ArrowUpTrayIcon from '@heroicons/react/24/outline/ArrowUpTrayIcon' +import TrashIcon from '@heroicons/react/24/solid/TrashIcon' +import imageCompression from 'browser-image-compression' import { useState } from 'react' import { useDropzone } from 'react-dropzone' @@ -10,34 +13,45 @@ interface Props { setState: React.Dispatch> } +const compressionOptions = { + maxSizeMB: 1, + maxWidthOrHeight: 1920, + useWebWorker: true, +} + export const GalleryForm = ({ state, setState }: Props) => { const appState = useAppState() - const [isUploading, setUploading] = useState(false) + const [uploadingImages, setUploadingImages] = useState([]) const upload = async (acceptedFiles: File[]) => { - setUploading(true) + setUploadingImages((files) => [...files, ...acceptedFiles]) - const assets = await Promise.all( - acceptedFiles.map(async (file) => { - return appState.assetsApi.upload(file, 'gallery') - }), - ) + const uploads = acceptedFiles.map(async (file) => { + const compressedFile = await imageCompression(file, compressionOptions) + return { + asset: await appState.assetsApi.upload(compressedFile, 'gallery'), + name: file.name, + } + }) - const newGalleryItems = assets.map((asset) => ({ - directus_files_id: { - id: asset.id, - width: 600, // TODO map to ids only - height: 400, - }, - })) + for await (const upload of uploads) { + setState((prevState) => ({ + ...prevState, + gallery: [ + ...prevState.gallery, + { + directus_files_id: { + id: upload.asset.id, + width: 0, + height: 0, + }, + }, + ], + })) - setUploading(false) - - setState((prevState) => ({ - ...prevState, - gallery: [...prevState.gallery, ...newGalleryItems], - })) + setUploadingImages((files) => files.filter((f) => f.name !== upload.name)) + } } const { getRootProps, getInputProps } = useDropzone({ @@ -48,12 +62,17 @@ export const GalleryForm = ({ state, setState }: Props) => { }, }) - const images = state.gallery.map((i, j) => ({ - src: appState.assetsApi.url + `${i.directus_files_id.id}.jpg`, - width: i.directus_files_id.width, - height: i.directus_files_id.height, - index: j, - })) + const images = state.gallery + .map((image) => ({ + src: appState.assetsApi.url + `${image.directus_files_id.id}.jpg`, + state: 'uploaded', + })) + .concat( + uploadingImages.map((file) => ({ + src: URL.createObjectURL(file), + state: 'uploading', + })), + ) const removeImage = (index: number) => { setState((prevState) => ({ @@ -63,32 +82,38 @@ export const GalleryForm = ({ state, setState }: Props) => { } return ( -
-
- - Drop here -
- - {isUploading &&
Uploading...
} - +
{images.map((image, index) => ( -
+
{`Gallery - + {image.state === 'uploading' && ( + + )} + {image.state === 'uploaded' && ( + + )}
))} + +
+ + +
) }