Can add and remove images from items
parent
5be94c3ddd
commit
3a7a6959e9
@ -0,0 +1,111 @@
|
||||
import React, {useState, useEffect, useMemo, useRef} from 'react'
|
||||
import {DateTime} from 'luxon'
|
||||
import axios from 'axios'
|
||||
import Link from 'next/link'
|
||||
|
||||
import {Button} from '@rmwc/button'
|
||||
import ActionBar from '~/components/admin/actionBar'
|
||||
import {Icon} from '@rmwc/icon'
|
||||
|
||||
import styles from '~/styles/imageGallery.module.css'
|
||||
|
||||
EditImages.getInitialProps = async ({ctx: {axios, query: {slug}}}) => {
|
||||
const {data: item} = await axios.get(`/api/items/by-slug/${slug}`)
|
||||
return {item}
|
||||
}
|
||||
|
||||
export default function EditImages({item: _item}) {
|
||||
const [item, setItem] = useState(_item)
|
||||
const fileRef = useRef()
|
||||
|
||||
// Only re-sort images whenever item changes
|
||||
const images = useMemo(() => item.images.slice(0).sort(
|
||||
(a,b) => DateTime.fromISO(a.date_uploaded).diff(DateTime.fromISO(b.date_uploaded))
|
||||
), [item])
|
||||
|
||||
|
||||
async function selectImage(ev){
|
||||
if(ev) ev.preventDefault()
|
||||
|
||||
fileRef.current.click()
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
fileRef.current.addEventListener('change', uploadImage)
|
||||
return ()=>fileRef.current.removeEventListener('change', uploadImage)
|
||||
}, [])
|
||||
|
||||
async function uploadImage(ev){
|
||||
if(ev) ev.preventDefault()
|
||||
|
||||
const image = await readFileAsync(ev.target)
|
||||
const {data: updatedItem} = await axios.post(`/api/items/${item.uuid}/images`, {image})
|
||||
setItem(updatedItem)
|
||||
}
|
||||
|
||||
function handleSetFeatured(image){
|
||||
return async (ev) => {
|
||||
if(ev) ev.preventDefault();
|
||||
|
||||
const {data: updatedItem} = await axios.post(`/api/images/${image.uuid}/featured`)
|
||||
setItem(updatedItem)
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemoveImage(image){
|
||||
return async (ev) => {
|
||||
if(ev) ev.preventDefault()
|
||||
|
||||
const {data: updatedItem} = await axios.delete(`/api/images/${image.uuid}`)
|
||||
setItem(updatedItem)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionBar title={`Editing images for "${item.name}"`}>
|
||||
<Button outlined onClick={selectImage}>Add Image</Button>
|
||||
</ActionBar>
|
||||
<Link href={`/admin/items/${item.urlslug}`}><a><Button icon="arrow_left">Back to {item.name}</Button></a></Link>
|
||||
<div className={styles.gallery}>
|
||||
{images.map(image => (
|
||||
<div key={image.uuid} className={styles.image}>
|
||||
<img key={image.uuid} src={`/api/images/${image.uuid}/thumb`}/>
|
||||
<div className={styles.details}>
|
||||
<div>
|
||||
<span>Uploaded {DateTime.fromISO(image.date_uploaded).toFormat('DD')}</span>
|
||||
<span>by {image.uploader.email}</span>
|
||||
</div>
|
||||
<button title="Set Featured" onClick={handleSetFeatured(image)} className="buttonLink"><Icon icon={image.featured?"star":"star_outline"}/></button>
|
||||
<button title="Remove Image" onClick={handleRemoveImage(image)} className="buttonLink"><Icon icon="delete"/></button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{!images.length ? (
|
||||
<>
|
||||
<p style={{marginTop: '100px'}}>No images added for this item.</p>
|
||||
<Button outlined onClick={selectImage}>Add One!</Button>
|
||||
</>
|
||||
) : (
|
||||
<p><Button outlined onClick={selectImage}>Add Image</Button></p>
|
||||
)}
|
||||
</div>
|
||||
<input ref={fileRef} className={styles.upload} type="file" accept="image/*" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function readFileAsync(fileInput){
|
||||
const reader = new FileReader()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
reader.addEventListener('load', () => {
|
||||
resolve(reader.result)
|
||||
})
|
||||
|
||||
reader.addEventListener('error', reject)
|
||||
reader.addEventListener('abort', reject)
|
||||
|
||||
reader.readAsDataURL(fileInput.files[0])
|
||||
})
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
.gallery {
|
||||
text-align: center;
|
||||
padding: 0 50px;
|
||||
--padding: 8px;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: inline-block;
|
||||
height: 300px;
|
||||
width: auto;
|
||||
margin: 10px;
|
||||
background: white;
|
||||
padding: var(--padding);
|
||||
box-shadow: 0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image img {
|
||||
min-width: 300px;
|
||||
background: rgb(77, 51, 80);
|
||||
object-fit: contain;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.image .details {
|
||||
position: absolute;
|
||||
height: 100px;
|
||||
left: var(--padding);
|
||||
right: var(--padding);
|
||||
bottom: var(--padding);
|
||||
background: linear-gradient(transparent, rgba(0,0,0,.3), black);
|
||||
color: white;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
padding-bottom: var(--padding);
|
||||
}
|
||||
|
||||
.details span {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.details div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.details button {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.details button:last-child {
|
||||
color: rgb(255, 122, 122) !important;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.upload {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
Loading…
Reference in New Issue