import { Component, createRef } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Chip from '@mui/material/Chip'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import IconButton from '@mui/material/IconButton'; import ContentPasteIcon from '@mui/icons-material/ContentPaste'; import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { article } from '../../api'; import { requireArray, requireObject } from '../../utils/apiShape'; import { articleWithPhotoOrder, articleWithUploadedPhoto, articleWithoutPhoto, formatPhotoDimensions, formatPhotoSizeKb, sortedPhotos, } from '../../utils/articleDetail'; import { notifyError } from '../../utils/notify'; import { pasteImageOrFallback } from '../../utils/pasteImage'; import { prepareArticlePhoto } from '../../utils/prepareArticlePhoto'; import ArticlePhoto from './ArticlePhoto'; const overlayChipSx = { height: 22, bgcolor: 'rgba(0, 0, 0, 0.65)', color: '#fff', '& .MuiChip-label': { px: 0.75, fontSize: 11 }, }; const overlayIconSx = { color: '#fff', bgcolor: 'rgba(0, 0, 0, 0.55)', '&:hover': { bgcolor: 'rgba(0, 0, 0, 0.75)' }, '&.Mui-disabled': { color: 'rgba(255,255,255,0.35)', bgcolor: 'rgba(0,0,0,0.35)' }, }; class ArticlePhotosDialog extends Component { constructor(props) { super(props); this.fileInputRef = createRef(); this.state = { busy: false }; this.handleClose = this.handleClose.bind(this); this.handlePasteClick = this.handlePasteClick.bind(this); this.handleFileChange = this.handleFileChange.bind(this); this.uploadRawImage = this.uploadRawImage.bind(this); } handleClose() { if (this.state.busy) return; this.props.onClose(); } async uploadRawImage(rawBlob) { const { article: articleData, onArticleUpdated } = this.props; this.setState({ busy: true }); try { const file = await prepareArticlePhoto(rawBlob); const uploadStarted = performance.now(); const res = await article.uploadImage(articleData.sku, file); console.log('[article-photo] upload', { uploadMs: Math.round(performance.now() - uploadStarted), bytes: file.size, sku: articleData.sku, }); requireObject(res, 'POST /api/article/:sku/image'); requireObject(res.image, 'image'); onArticleUpdated?.(articleWithUploadedPhoto(articleData, res.image)); } catch (err) { notifyError(err); } finally { this.setState({ busy: false }); } } handlePasteClick() { if (this.state.busy) return; void pasteImageOrFallback(() => { this.fileInputRef.current?.click(); }).then((image) => { if (image) void this.uploadRawImage(image); }); } handleFileChange(event) { const file = event.target.files?.[0]; event.target.value = ''; if (!file) return; void this.uploadRawImage(file); } async deletePhoto(fullHash) { const { article: articleData, onArticleUpdated } = this.props; this.setState({ busy: true }); try { await article.deleteImage(articleData.sku, fullHash); onArticleUpdated?.(articleWithoutPhoto(articleData, fullHash)); } catch (err) { notifyError(err); } finally { this.setState({ busy: false }); } } async movePhoto(fullHash, delta) { const { article: articleData, onArticleUpdated } = this.props; const photos = sortedPhotos(articleData); const index = photos.findIndex((row) => row.fullHash === fullHash); const target = index + delta; if (index < 0 || target < 0 || target >= photos.length) return; const next = [...photos]; const [row] = next.splice(index, 1); next.splice(target, 0, row); const fullHashes = next.map((item) => item.fullHash); this.setState({ busy: true }); try { const res = await article.orderImages(articleData.sku, fullHashes); requireObject(res, 'PUT /api/article/:sku/images/order'); requireArray(res.fullHashes, 'fullHashes'); onArticleUpdated?.(articleWithPhotoOrder(articleData, res.fullHashes)); } catch (err) { notifyError(err); } finally { this.setState({ busy: false }); } } renderPhotoTile(photo, index, total) { const { busy } = this.state; const hasDimensions = photo.fullW != null && photo.fullH != null; const hasSize = photo.fullSize != null; return ( {hasDimensions ? ( ) : null} {hasSize ? ( ) : null} void this.movePhoto(photo.fullHash, -1)} sx={overlayIconSx} > = total - 1} onClick={() => void this.movePhoto(photo.fullHash, 1)} sx={overlayIconSx} > void this.deletePhoto(photo.fullHash)} sx={overlayIconSx} > ); } render() { const { open, article: articleData } = this.props; const { busy } = this.state; const photos = sortedPhotos(articleData); return ( <> Bilder {photos.length === 0 ? ( Keine Bilder. ) : ( {photos.map((photo, index) => this.renderPhotoTile(photo, index, photos.length), )} )} ); } } export default ArticlePhotosDialog;