mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 07:38:07 -05:00
adding distinct object to redux/preview.js state to track studio membership
This commit is contained in:
parent
52281502b2
commit
ffe5e8cb43
3 changed files with 138 additions and 174 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue