Include modifying the studio image

This commit is contained in:
Paul Kaplan 2021-04-26 09:04:13 -04:00
parent 647575e4a8
commit 430f67c0e1
4 changed files with 106 additions and 3 deletions

View file

@ -10,13 +10,15 @@
const keyMirror = require('keymirror'); const keyMirror = require('keymirror');
const api = require('../lib/api'); const api = require('../lib/api');
const {selectUsername} = require('./session'); const {selectUsername} = require('./session');
const {selectStudioId} = require('./studio'); const {selectStudioId, selectStudioImage} = require('./studio');
const Errors = keyMirror({ const Errors = keyMirror({
NETWORK: null, NETWORK: null,
SERVER: null, SERVER: null,
INAPPROPRIATE: null, INAPPROPRIATE: null,
PERMISSION: null, PERMISSION: null,
THUMBNAIL_TOO_LARGE: null,
THUMBNAIL_MISSING: null,
UNHANDLED: null UNHANDLED: null
}); });
@ -80,6 +82,8 @@ const selectIsMutatingFollowing = state => state.studioMutations.isMutating.foll
const selectTitleMutationError = state => state.studioMutations.mutationErrors.title; const selectTitleMutationError = state => state.studioMutations.mutationErrors.title;
const selectDescriptionMutationError = state => state.studioMutations.mutationErrors.description; const selectDescriptionMutationError = state => state.studioMutations.mutationErrors.description;
const selectFollowingMutationError = state => state.studioMutations.mutationErrors.following; const selectFollowingMutationError = state => state.studioMutations.mutationErrors.following;
const selectIsMutatingImage = state => state.studioMutations.isMutating.image;
const selectImageMutationError = state => state.studioMutations.mutationErrors.image;
// Thunks // Thunks
/** /**
@ -97,6 +101,8 @@ const normalizeError = (err, body, res) => {
try { try {
if (body.errors.length > 0) { if (body.errors.length > 0) {
if (body.errors[0] === 'inappropriate-generic') return Errors.INAPPROPRIATE; if (body.errors[0] === 'inappropriate-generic') return Errors.INAPPROPRIATE;
if (body.errors[0] === 'thumbnail-too-large') return Errors.THUMBNAIL_TOO_LARGE;
if (body.errors[0] === 'thumbnail-missing') return Errors.THUMBNAIL_MISSING;
return Errors.UNHANDLED; return Errors.UNHANDLED;
} }
} catch (_) { /* No body.errors[], continue */ } } catch (_) { /* No body.errors[], continue */ }
@ -154,6 +160,26 @@ const mutateFollowingStudio = shouldFollow => ((dispatch, getState) => {
}); });
}); });
const mutateStudioImage = input => ((dispatch, getState) => {
const state = getState();
const studioId = selectStudioId(state);
const currentImage = selectStudioImage(state);
dispatch(startMutation('image'));
const formData = new FormData();
formData.append('file', input.files[0]);
api({
host: '',
uri: `/site-api/galleries/all/${studioId}/`,
method: 'POST',
withCredentials: true,
useCsrf: true,
body: formData
}, (err, body, res) => {
const error = normalizeError(err, body, res);
dispatch(completeMutation('image', error ? currentImage : body.thumbnail_url, error));
});
});
module.exports = { module.exports = {
getInitialState, getInitialState,
studioMutationsReducer, studioMutationsReducer,
@ -163,6 +189,7 @@ module.exports = {
mutateStudioTitle, mutateStudioTitle,
mutateStudioDescription, mutateStudioDescription,
mutateFollowingStudio, mutateFollowingStudio,
mutateStudioImage,
// Selectors // Selectors
selectIsMutatingTitle, selectIsMutatingTitle,
@ -170,5 +197,7 @@ module.exports = {
selectIsMutatingFollowing, selectIsMutatingFollowing,
selectTitleMutationError, selectTitleMutationError,
selectDescriptionMutationError, selectDescriptionMutationError,
selectFollowingMutationError selectFollowingMutationError,
selectIsMutatingImage,
selectImageMutationError
}; };

View file

@ -18,7 +18,7 @@ const getInitialState = () => ({
description: '', description: '',
openToAll: false, openToAll: false,
commentingAllowed: false, commentingAllowed: false,
thumbnail: '', image: '',
followers: 0, followers: 0,
owner: null, owner: null,
@ -86,6 +86,7 @@ const setRoles = roles => ({
const selectStudioId = state => state.studio.id; const selectStudioId = state => state.studio.id;
const selectStudioTitle = state => state.studio.title; const selectStudioTitle = state => state.studio.title;
const selectStudioDescription = state => state.studio.description; const selectStudioDescription = state => state.studio.description;
const selectStudioImage = state => state.studio.image;
const selectIsLoadingInfo = state => state.studio.infoStatus === Status.FETCHING; const selectIsLoadingInfo = state => state.studio.infoStatus === Status.FETCHING;
const selectIsFollowing = state => state.studio.following; const selectIsFollowing = state => state.studio.following;
const selectIsLoadingRoles = state => state.studio.rolesStatus === Status.FETCHING; const selectIsLoadingRoles = state => state.studio.rolesStatus === Status.FETCHING;
@ -103,6 +104,7 @@ const getInfo = () => ((dispatch, getState) => {
dispatch(setInfo({ dispatch(setInfo({
title: body.title, title: body.title,
description: body.description, description: body.description,
image: body.image,
openToAll: body.open_to_all, openToAll: body.open_to_all,
commentingAllowed: body.commenting_allowed, commentingAllowed: body.commenting_allowed,
updated: new Date(body.history.modified), updated: new Date(body.history.modified),
@ -150,6 +152,7 @@ module.exports = {
selectStudioId, selectStudioId,
selectStudioTitle, selectStudioTitle,
selectStudioDescription, selectStudioDescription,
selectStudioImage,
selectIsLoadingInfo, selectIsLoadingInfo,
selectIsLoadingRoles, selectIsLoadingRoles,
selectIsFollowing selectIsFollowing

View file

@ -0,0 +1,69 @@
/* eslint-disable react/jsx-no-bind */
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {selectStudioImage, selectIsLoadingInfo} from '../../redux/studio';
import {selectCanEditInfo} from '../../redux/studio-permissions';
import {
mutateStudioImage, selectIsMutatingImage, selectImageMutationError
} from '../../redux/studio-mutations';
import Spinner from '../../components/spinner/spinner.jsx';
const StudioImage = ({
imageError, isLoading, isMutating, image, canEditInfo, handleUpdate
}) => (
<div>
<h3>Image</h3>
{isLoading ? (
<h4>Loading...</h4>
) : (
<div>
<div style={{width: '200px', height: '150px', border: '1px solid green'}}>
{isMutating ?
<Spinner color="blue" /> :
<img
style={{objectFit: 'contain'}}
src={image}
/>}
</div>
{canEditInfo &&
<label>
<input
disabled={isMutating}
type="file"
accept="image/*"
onChange={e => {
handleUpdate(e.target);
e.target.value = '';
}}
/>
{imageError && <div>Error mutating image: {imageError}</div>}
</label>
}
</div>
)}
</div>
);
StudioImage.propTypes = {
imageError: PropTypes.string,
canEditInfo: PropTypes.bool,
isLoading: PropTypes.bool,
isMutating: PropTypes.bool,
image: PropTypes.string,
handleUpdate: PropTypes.func
};
export default connect(
state => ({
image: selectStudioImage(state),
canEditInfo: selectCanEditInfo(state),
isLoading: selectIsLoadingInfo(state),
isMutating: selectIsMutatingImage(state),
imageError: selectImageMutationError(state)
}),
{
handleUpdate: mutateStudioImage
}
)(StudioImage);

View file

@ -5,6 +5,7 @@ import Debug from './debug.jsx';
import StudioDescription from './studio-description.jsx'; import StudioDescription from './studio-description.jsx';
import StudioFollow from './studio-follow.jsx'; import StudioFollow from './studio-follow.jsx';
import StudioTitle from './studio-title.jsx'; import StudioTitle from './studio-title.jsx';
import StudioImage from './studio-image.jsx';
import {selectIsLoggedIn} from '../../redux/session'; import {selectIsLoggedIn} from '../../redux/session';
import {getInfo, getRoles} from '../../redux/studio'; import {getInfo, getRoles} from '../../redux/studio';
@ -26,6 +27,7 @@ const StudioInfo = ({
<StudioTitle /> <StudioTitle />
<StudioDescription /> <StudioDescription />
<StudioFollow /> <StudioFollow />
<StudioImage />
<Debug <Debug
label="Studio Info" label="Studio Info"
data={studio} data={studio}