mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-04-05 00:56:32 +00:00
Compress images, show images while uploading
This commit is contained in:
parent
b87a12a1d8
commit
85f3141a42
16
package-lock.json
generated
16
package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@tanstack/react-query": "^5.17.8",
|
"@tanstack/react-query": "^5.17.8",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
|
"browser-image-compression": "^2.0.2",
|
||||||
"date-fns": "^3.3.1",
|
"date-fns": "^3.3.1",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet.locatecontrol": "^0.79.0",
|
"leaflet.locatecontrol": "^0.79.0",
|
||||||
@ -3460,6 +3461,15 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.24.4",
|
"version": "4.24.4",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
|
||||||
@ -12443,6 +12453,12 @@
|
|||||||
"uuid": "dist/bin/uuid"
|
"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": {
|
"node_modules/verror": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
|
|||||||
@ -99,6 +99,7 @@
|
|||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@tanstack/react-query": "^5.17.8",
|
"@tanstack/react-query": "^5.17.8",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
|
"browser-image-compression": "^2.0.2",
|
||||||
"date-fns": "^3.3.1",
|
"date-fns": "^3.3.1",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet.locatecontrol": "^0.79.0",
|
"leaflet.locatecontrol": "^0.79.0",
|
||||||
|
|||||||
@ -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 { useState } from 'react'
|
||||||
import { useDropzone } from 'react-dropzone'
|
import { useDropzone } from 'react-dropzone'
|
||||||
|
|
||||||
@ -10,34 +13,45 @@ interface Props {
|
|||||||
setState: React.Dispatch<React.SetStateAction<FormState>>
|
setState: React.Dispatch<React.SetStateAction<FormState>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const compressionOptions = {
|
||||||
|
maxSizeMB: 1,
|
||||||
|
maxWidthOrHeight: 1920,
|
||||||
|
useWebWorker: true,
|
||||||
|
}
|
||||||
|
|
||||||
export const GalleryForm = ({ state, setState }: Props) => {
|
export const GalleryForm = ({ state, setState }: Props) => {
|
||||||
const appState = useAppState()
|
const appState = useAppState()
|
||||||
|
|
||||||
const [isUploading, setUploading] = useState(false)
|
const [uploadingImages, setUploadingImages] = useState<File[]>([])
|
||||||
|
|
||||||
const upload = async (acceptedFiles: File[]) => {
|
const upload = async (acceptedFiles: File[]) => {
|
||||||
setUploading(true)
|
setUploadingImages((files) => [...files, ...acceptedFiles])
|
||||||
|
|
||||||
const assets = await Promise.all(
|
const uploads = acceptedFiles.map(async (file) => {
|
||||||
acceptedFiles.map(async (file) => {
|
const compressedFile = await imageCompression(file, compressionOptions)
|
||||||
return appState.assetsApi.upload(file, 'gallery')
|
return {
|
||||||
}),
|
asset: await appState.assetsApi.upload(compressedFile, 'gallery'),
|
||||||
)
|
name: file.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const newGalleryItems = assets.map((asset) => ({
|
for await (const upload of uploads) {
|
||||||
directus_files_id: {
|
setState((prevState) => ({
|
||||||
id: asset.id,
|
...prevState,
|
||||||
width: 600, // TODO map to ids only
|
gallery: [
|
||||||
height: 400,
|
...prevState.gallery,
|
||||||
},
|
{
|
||||||
}))
|
directus_files_id: {
|
||||||
|
id: upload.asset.id,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}))
|
||||||
|
|
||||||
setUploading(false)
|
setUploadingImages((files) => files.filter((f) => f.name !== upload.name))
|
||||||
|
}
|
||||||
setState((prevState) => ({
|
|
||||||
...prevState,
|
|
||||||
gallery: [...prevState.gallery, ...newGalleryItems],
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
@ -48,12 +62,17 @@ export const GalleryForm = ({ state, setState }: Props) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const images = state.gallery.map((i, j) => ({
|
const images = state.gallery
|
||||||
src: appState.assetsApi.url + `${i.directus_files_id.id}.jpg`,
|
.map((image) => ({
|
||||||
width: i.directus_files_id.width,
|
src: appState.assetsApi.url + `${image.directus_files_id.id}.jpg`,
|
||||||
height: i.directus_files_id.height,
|
state: 'uploaded',
|
||||||
index: j,
|
}))
|
||||||
}))
|
.concat(
|
||||||
|
uploadingImages.map((file) => ({
|
||||||
|
src: URL.createObjectURL(file),
|
||||||
|
state: 'uploading',
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
const removeImage = (index: number) => {
|
const removeImage = (index: number) => {
|
||||||
setState((prevState) => ({
|
setState((prevState) => ({
|
||||||
@ -63,32 +82,38 @@ export const GalleryForm = ({ state, setState }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='tw:h-full tw:flex tw:flex-col tw:mt-4'>
|
<div className='tw:flex tw:flex-wrap tw:gap-4 tw:my-4'>
|
||||||
<div
|
|
||||||
{...getRootProps()}
|
|
||||||
className='tw:cursor-pointer tw:border tw:border-dashed tw:border-gray-300 tw:p-4 tw:rounded-lg'
|
|
||||||
>
|
|
||||||
<input {...getInputProps()} />
|
|
||||||
Drop here
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isUploading && <div className='tw:mt-2 tw:text-gray-500'>Uploading...</div>}
|
|
||||||
|
|
||||||
{images.map((image, index) => (
|
{images.map((image, index) => (
|
||||||
<div key={index} className='tw:mb-2'>
|
<div key={index} className='tw:relative'>
|
||||||
<img
|
<img
|
||||||
src={image.src}
|
src={image.src}
|
||||||
alt={`Gallery image ${index + 1}`}
|
alt={`Gallery image ${index + 1}`}
|
||||||
className='tw:w-full tw:h-auto tw:rounded-lg'
|
className={`tw:w-60 tw:h-60 tw:object-cover tw:rounded-lg ${
|
||||||
|
image.state === 'uploading' ? 'tw:opacity-50' : ''
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
<button
|
{image.state === 'uploading' && (
|
||||||
className='tw:mt-2 tw:bg-red-500 tw:text-white tw:px-4 tw:py-2 tw:rounded'
|
<span className='tw:loading tw:loading-spinner tw:absolute tw:inset-0 tw:m-auto'></span>
|
||||||
onClick={() => removeImage(index)}
|
)}
|
||||||
>
|
{image.state === 'uploaded' && (
|
||||||
Remove
|
<button
|
||||||
</button>
|
className='tw:m-2 tw:bg-red-500 tw:text-white tw:p-2 tw:rounded-full tw:absolute tw:top-0 tw:right-0 tw:hover:bg-red-600'
|
||||||
|
onClick={() => removeImage(index)}
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
<TrashIcon className='tw:h-5 tw:w-5' />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<div
|
||||||
|
{...getRootProps()}
|
||||||
|
className='tw:flex tw:center tw:w-60 tw:h-60 tw:cursor-pointer tw:border tw:border-dashed tw:border-gray-300 tw:p-4 tw:rounded-lg'
|
||||||
|
>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
<ArrowUpTrayIcon className='tw:h-8 tw:w-8 tw:m-auto' />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user