diff --git a/src/components/modal/addtostudio/presentation.jsx b/src/components/modal/addtostudio/presentation.jsx index 6f8b87b7c..72add1067 100644 --- a/src/components/modal/addtostudio/presentation.jsx +++ b/src/components/modal/addtostudio/presentation.jsx @@ -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 = checkmark-icon - const plus = plus-icon - const studioButtons = this.props.studios.map((studio, index) => { - return ( -
this.props.onToggleStudio(studio.id)} +const AddToStudioModalPresentation = ({ + isOpen, + studios, + waitingToClose, + onToggleStudio, + onRequestClose, + onSubmit +}) => { + const contentLabel = this.props.intl.formatMessage({id: "addToStudio.title"}); + const checkmark = checkmark-icon + const plus = plus-icon + const studioButtons = this.props.studios.map((studio, index) => { + return ( +
this.props.onToggleStudio(studio.id)} + > +
+ {truncate(studio.title, {'length': 20, 'separator': /[,:\.;]*\s+/})} +
+
-
- {truncate(studio.title, {'length': 20, 'separator': /[,:\.;]*\s+/})} -
-
- {(studio.hasRequestOutstanding ? - () : - (studio.includesProject ? checkmark : plus))} + {(studio.hasRequestOutstanding ? + () : + (studio.includesProject ? checkmark : plus))} +
+
+ ); + }); + + return ( + +
+
+
+ {contentLabel}
- ); - }); - - return ( - -
-
-
- {contentLabel} +
+
+
+
+ {studioButtons} +
+
+
-
-
-
-
- {studioButtons} + + +
+ +
-
-
-
- - - - + + {this.props.waitingToClose ? [ - {this.props.waitingToClose ? [ - - ] : [ - - ]} - - -
+ ] : [ + + ]} + +
- - ); - } +
+ + ); } AddToStudioModalPresentation.propTypes = { diff --git a/src/redux/preview.js b/src/redux/preview.js index 59bb2e565..94c54cf39 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -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)); }); }); diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 993de356e..673048141 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -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