131 lines
3.8 KiB
JavaScript
131 lines
3.8 KiB
JavaScript
import { Component, createRef } from 'react';
|
|
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
|
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
|
import { StorageManagerContext } from '../../api/storageManagerContext';
|
|
import { PanelTile } from '../panels';
|
|
import { requireObject } from '../../utils/apiShape';
|
|
import { notifyError } from '../../utils/notify';
|
|
import { prepareArticlePhoto } from '../../utils/prepareArticlePhoto';
|
|
import { pasteImageOrFallback } from '../../utils/pasteImage';
|
|
import { articleWithUploadedPhoto, primaryPhoto } from '../../utils/articleDetail';
|
|
import ArticlePhoto from './ArticlePhoto';
|
|
import ArticlePhotosDialog from './ArticlePhotosDialog';
|
|
|
|
class ArticlePhotoTile extends Component {
|
|
static contextType = StorageManagerContext;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.fileInputRef = createRef();
|
|
this.state = { busy: false, editOpen: false };
|
|
this.handlePasteClick = this.handlePasteClick.bind(this);
|
|
this.handleFileChange = this.handleFileChange.bind(this);
|
|
this.handleEditOpen = this.handleEditOpen.bind(this);
|
|
this.handleEditClose = this.handleEditClose.bind(this);
|
|
this.uploadRawImage = this.uploadRawImage.bind(this);
|
|
}
|
|
|
|
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 this.context.article.addImage(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);
|
|
}
|
|
|
|
handleEditOpen() {
|
|
this.setState({ editOpen: true });
|
|
}
|
|
|
|
handleEditClose() {
|
|
this.setState({ editOpen: false });
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
article: articleData,
|
|
onArticleUpdated,
|
|
wide = 1,
|
|
minHeight = 200,
|
|
sx,
|
|
tileKey,
|
|
} = this.props;
|
|
const { busy, editOpen } = this.state;
|
|
const photo = primaryPhoto(articleData);
|
|
const hasPhoto = photo?.thumbHash != null || photo?.fullHash != null;
|
|
|
|
return (
|
|
<>
|
|
<PanelTile
|
|
wide={wide}
|
|
minHeight={minHeight}
|
|
sx={sx}
|
|
tileKey={tileKey}
|
|
action={
|
|
hasPhoto
|
|
? {
|
|
label: 'Bilder bearbeiten',
|
|
icon: EditOutlinedIcon,
|
|
onClick: this.handleEditOpen,
|
|
busy: false,
|
|
}
|
|
: {
|
|
label: 'Bild einfügen',
|
|
icon: ContentPasteIcon,
|
|
onClick: this.handlePasteClick,
|
|
busy,
|
|
}
|
|
}
|
|
>
|
|
<ArticlePhoto photo={photo} />
|
|
</PanelTile>
|
|
<input
|
|
ref={this.fileInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
hidden
|
|
onChange={this.handleFileChange}
|
|
/>
|
|
<ArticlePhotosDialog
|
|
open={editOpen}
|
|
article={articleData}
|
|
onClose={this.handleEditClose}
|
|
onArticleUpdated={onArticleUpdated}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default ArticlePhotoTile;
|