adding distinct object to redux/preview.js state to track studio membership

This commit is contained in:
Ben Wheeler 2018-07-24 09:12:13 -04:00
parent 52281502b2
commit ffe5e8cb43
3 changed files with 138 additions and 174 deletions

View file

@ -17,123 +17,117 @@ const FlexRow = require('../../flex-row/flex-row.jsx');
require('../../forms/button.scss');
require('./modal.scss');
class AddToStudioModalPresentation extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleSubmit'
]);
}
handleSubmit (formData) {
this.props.onSubmit(formData);
}
render () {
const contentLabel = this.props.intl.formatMessage({id: "addToStudio.title"});
const checkmark = <img alt="checkmark-icon"
className="studio-status-icon-checkmark-img"
src="/svgs/modal/confirm.svg"
/>
const plus = <img alt="plus-icon"
className="studio-status-icon-plus-img"
src="/svgs/modal/add.svg"
/>
const studioButtons = this.props.studios.map((studio, index) => {
return (
<div className={"studio-selector-button " +
(studio.hasRequestOutstanding ? "studio-selector-button-waiting" :
(studio.includesProject ? "studio-selector-button-selected" : ""))}
key={studio.id}
onClick={() => this.props.onToggleStudio(studio.id)}
const AddToStudioModalPresentation = ({
isOpen,
studios,
waitingToClose,
onToggleStudio,
onRequestClose,
onSubmit
}) => {
const contentLabel = this.props.intl.formatMessage({id: "addToStudio.title"});
const checkmark = <img alt="checkmark-icon"
className="studio-status-icon-checkmark-img"
src="/svgs/modal/confirm.svg"
/>
const plus = <img alt="plus-icon"
className="studio-status-icon-plus-img"
src="/svgs/modal/add.svg"
/>
const studioButtons = this.props.studios.map((studio, index) => {
return (
<div className={"studio-selector-button " +
(studio.hasRequestOutstanding ? "studio-selector-button-waiting" :
(studio.includesProject ? "studio-selector-button-selected" : ""))}
key={studio.id}
onClick={() => this.props.onToggleStudio(studio.id)}
>
<div className={"studio-selector-button-text " +
(studio.includesProject ? "studio-selector-button-text-selected" :
"studio-selector-button-text-unselected")}>
{truncate(studio.title, {'length': 20, 'separator': /[,:\.;]*\s+/})}
</div>
<div className={"studio-status-icon " +
(studio.includesProject ? "" : "studio-status-icon-unselected")}
>
<div className={"studio-selector-button-text " +
(studio.includesProject ? "studio-selector-button-text-selected" :
"studio-selector-button-text-unselected")}>
{truncate(studio.title, {'length': 20, 'separator': /[,:\.;]*\s+/})}
</div>
<div className={"studio-status-icon " +
(studio.includesProject ? "" : "studio-status-icon-unselected")}
>
{(studio.hasRequestOutstanding ?
(<Spinner type="smooth" />) :
(studio.includesProject ? checkmark : plus))}
{(studio.hasRequestOutstanding ?
(<Spinner type="smooth" />) :
(studio.includesProject ? checkmark : plus))}
</div>
</div>
);
});
return (
<Modal
className="mod-addToStudio"
contentLabel={contentLabel}
onRequestClose={this.props.onRequestClose}
isOpen={this.props.isOpen}
>
<div>
<div className="addToStudio-modal-header">
<div className="addToStudio-content-label">
{contentLabel}
</div>
</div>
);
});
return (
<Modal
className="mod-addToStudio"
contentLabel={contentLabel}
onRequestClose={this.props.onRequestClose}
isOpen={this.props.isOpen}
>
<div>
<div className="addToStudio-modal-header">
<div className="addToStudio-content-label">
{contentLabel}
<div className="addToStudio-modal-content">
<div className="studio-list-outer-scrollbox">
<div className="studio-list-inner-scrollbox">
<div className="studio-list-container">
{studioButtons}
</div>
</div>
<div className="studio-list-bottom-gradient">
</div>
</div>
<div className="addToStudio-modal-content">
<div className="studio-list-outer-scrollbox">
<div className="studio-list-inner-scrollbox">
<div className="studio-list-container">
{studioButtons}
<Form
className="add-to-studio"
onSubmit={this.props.onSubmit}
>
<FlexRow className="action-buttons">
<Button
className="action-button close-button white"
onClick={this.props.onRequestClose}
key="closeButton"
name="closeButton"
type="button"
>
<div className="action-button-text">
<FormattedMessage id="general.close" />
</div>
</div>
<div className="studio-list-bottom-gradient">
</div>
</div>
<Form
className="add-to-studio"
onSubmit={this.handleSubmit}
>
<FlexRow className="action-buttons">
</Button>
{this.props.waitingToClose ? [
<Button
className="action-button close-button white"
onClick={this.props.onRequestClose}
key="closeButton"
name="closeButton"
type="button"
className="action-button submit-button submit-button-waiting"
disabled="disabled"
key="submitButton"
type="submit"
>
<div className="action-button-text">
<FormattedMessage id="general.close" />
<Spinner type="smooth" />
<FormattedMessage id="addToStudio.finishing" />
</div>
</Button>
{this.props.waitingToClose ? [
<Button
className="action-button submit-button submit-button-waiting"
disabled="disabled"
key="submitButton"
type="submit"
>
<div className="action-button-text">
<Spinner type="smooth" />
<FormattedMessage id="addToStudio.finishing" />
</div>
</Button>
] : [
<Button
className="action-button submit-button"
key="submitButton"
type="submit"
>
<div className="action-button-text">
<FormattedMessage id="general.okay" />
</div>
</Button>
]}
</FlexRow>
</Form>
</div>
] : [
<Button
className="action-button submit-button"
key="submitButton"
type="submit"
>
<div className="action-button-text">
<FormattedMessage id="general.okay" />
</div>
</Button>
]}
</FlexRow>
</Form>
</div>
</Modal>
);
}
</div>
</Modal>
);
}
AddToStudioModalPresentation.propTypes = {

View file

@ -31,7 +31,8 @@ module.exports.getInitialState = () => ({
original: {},
parent: {},
projectStudios: [],
curatedStudios: []
curatedStudios: [],
currentStudioIds: new Set()
});
module.exports.previewReducer = (state, action) => {
@ -57,49 +58,23 @@ module.exports.previewReducer = (state, action) => {
parent: action.info
});
case 'SET_PROJECT_STUDIOS':
// alter the returned object so that each studio object in the array
// includes an additional property indicating that initially, this
// studio includes the project. This is important because if it is a
// studio open to the public, which the user does not curate or own,
// and the user removes the project from that studio, we don't want
// to forget about the studio completely!
// also initialize currentStudioIds, to keep track of which studios
// the project is currently in.
return Object.assign({}, state, {
projectStudios: action.items.map(studio => (
Object.assign({}, studio, {includesProject: true})
))
projectStudios: action.items,
currentStudioIds: new Set(action.items.map(studio => studio.id))
});
case 'SET_CURATED_STUDIOS':
return Object.assign({}, state, {curatedStudios: action.items});
case 'ADD_PROJECT_TO_STUDIO':
// add studio id to our studios-that-this-project-belongs-to set.
return Object.assign({}, state, {
curatedStudios: action.items
currentStudioIds: new Set(state.currentStudioIds.add(action.studioId))
});
case 'ADD_TO_PROJECT_STUDIOS':
// add studio to our studios-that-this-project-belongs-to list.
// Server response doesn't include full studio object, so just use a
// minimal stub object.
case 'REMOVE_PROJECT_FROM_STUDIO':
state.currentStudioIds.delete(action.studioId);
return Object.assign({}, state, {
projectStudios: state.projectStudios.some(studio => (
studio.id === action.studioId
)) ?
state.projectStudios.map(studio => {
if (studio.id === action.studioId) {
studio.includesProject = true;
}
return Object.assign({}, studio);
}) : state.projectStudios.concat(
{
id: action.studioId,
includesProject: true
}
)
});
case 'REMOVE_FROM_PROJECT_STUDIOS':
return Object.assign({}, state, {
projectStudios: state.projectStudios.map(studio => {
if (studio.id === action.studioId) {
studio.includesProject = false;
}
return Object.assign({}, studio);
})
currentStudioIds: new Set(state.currentStudioIds)
});
case 'SET_COMMENTS':
return Object.assign({}, state, {
@ -174,13 +149,13 @@ module.exports.setCuratedStudios = items => ({
items: items
});
module.exports.addToProjectStudios = studioId => ({
type: 'ADD_TO_PROJECT_STUDIOS',
module.exports.addProjectToStudio = studioId => ({
type: 'ADD_PROJECT_TO_STUDIO',
studioId: studioId
});
module.exports.removeFromProjectStudios = studioId => ({
type: 'REMOVE_FROM_PROJECT_STUDIOS',
module.exports.removeProjectFromStudio = studioId => ({
type: 'REMOVE_PROJECT_FROM_STUDIO',
studioId: studioId
});
@ -465,7 +440,7 @@ module.exports.addToStudio = (studioId, projectId, token) => (dispatch => {
return;
}
dispatch(module.exports.setStudioFetchStatus(studioId, module.exports.Status.FETCHED));
dispatch(module.exports.addToProjectStudios(studioId));
dispatch(module.exports.addProjectToStudio(studioId));
});
});
@ -485,7 +460,7 @@ module.exports.leaveStudio = (studioId, projectId, token) => (dispatch => {
return;
}
dispatch(module.exports.setStudioFetchStatus(studioId, module.exports.Status.FETCHED));
dispatch(module.exports.removeFromProjectStudios(studioId));
dispatch(module.exports.removeProjectFromStudio(studioId));
});
});

View file

@ -395,34 +395,28 @@ Preview.defaultProps = {
};
// Build consolidated curatedStudios object from all studio info.
// We add data to curatedStudios so it knows which of the studios the
// project belongs to, and the status of requests to join/leave studios.
function consolidateStudiosInfo (curatedStudios, projectStudios, studioRequests) {
let consolidatedStudios = [];
let projectStudiosFoundInCurated = {}; // temp, for time complexity
// We add flags to indicate whether the project is currently in the studio,
// and the status of requests to join/leave studios.
function consolidateStudiosInfo (curatedStudios, projectStudios,
currentStudioIds, studioRequests) {
const consolidatedStudios = [];
// copy curated studios, updating any that are also in other data structures
curatedStudios.forEach(curatedStudio => {
let studioCopy = Object.assign({}, curatedStudio, {includesProject: false});
projectStudios.some(projectStudio => {
if (curatedStudio.id === projectStudio.id) {
studioCopy.includesProject = projectStudio.includesProject;
projectStudiosFoundInCurated[projectStudio.id] = true;
return true; // break out of the Array.some loop
}
});
consolidatedStudios.push(studioCopy);
});
// if there are any other studios this project is in that are NOT in
// the list of studios this user curates, like anyone-can-add-their-project
// studios, add to front of list
projectStudios.forEach(projectStudio => {
if (!(projectStudio.id in projectStudiosFoundInCurated)) {
// no need to specify includesProject = true or false, because
// that state is managed by redux actions.
consolidatedStudios.unshift(Object.assign({}, projectStudio));
const includesProject = currentStudioIds.has(projectStudio.id);
const consolidatedStudio =
Object.assign({}, projectStudio, {includesProject: includesProject});
consolidatedStudios.push(consolidatedStudio);
});
// copy the curated studios that project is not in
curatedStudios.forEach(curatedStudio => {
if (!currentStudioIds.has(curatedStudio.id)) {
const consolidatedStudio =
Object.assign({}, curatedStudio, {includesProject: false});
consolidatedStudios.push(consolidatedStudio);
}
});
// set studio state to hasRequestOutstanding==true if it's being fetched,
// false if it's not
consolidatedStudios.forEach(consolidatedStudio => {
@ -445,7 +439,8 @@ const mapStateToProps = state => ({
sessionStatus: state.session.status,
projectStudios: state.preview.projectStudios,
studios: consolidateStudiosInfo(state.preview.curatedStudios,
state.preview.projectStudios, state.preview.status.studioRequests),
state.preview.projectStudios, state.preview.currentStudioIds,
state.preview.status.studioRequests),
user: state.session.session.user,
playerMode: state.scratchGui.mode.isPlayerOnly,
fullScreen: state.scratchGui.mode.isFullScreen