From a6409bbcce65d851761141efa3a14eb492acdc10 Mon Sep 17 00:00:00 2001 From: Benjamin Wheeler Date: Fri, 19 Oct 2018 16:02:59 -0400 Subject: [PATCH] Pass to and receive from GUI info about project creation lifecycle; handle url changes (#2197) * add canSaveNew prop to pass to GUI * pass to and receive from GUI info about project lifecycle * reset project data or fetch new project data depending on updates received from gui * removed canSaveNew * projectId always a string * moved handleUpdateProjectId calls that fetch or set project metadata into componentDidUpdate * changed page history object * removed comments * two small fixes to deal with edge cases * cleaning up getExtensions --- src/redux/preview.js | 6 ++ src/views/preview/preview.jsx | 173 ++++++++++++++++++++++------------ 2 files changed, 117 insertions(+), 62 deletions(-) diff --git a/src/redux/preview.js b/src/redux/preview.js index 237897855..2827cf58a 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -46,6 +46,8 @@ module.exports.previewReducer = (state, action) => { } switch (action.type) { + case 'RESET_TO_INTIAL_STATE': + return module.exports.getInitialState(); case 'SET_PROJECT_INFO': return Object.assign({}, state, { projectInfo: action.info @@ -164,6 +166,10 @@ module.exports.setError = error => ({ error: error }); +module.exports.resetProject = () => ({ + type: 'RESET_TO_INTIAL_STATE' +}); + module.exports.setProjectInfo = info => ({ type: 'SET_PROJECT_INFO', info: info diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 2db3b6873..4afd260d7 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -33,6 +33,7 @@ class Preview extends React.Component { super(props); bindAll(this, [ 'addEventListeners', + 'fetchCommunityData', 'handleAddComment', 'handleDeleteComment', 'handleToggleStudio', @@ -49,6 +50,7 @@ class Preview extends React.Component { 'handleAddToStudioClose', 'handleSeeInside', 'handleShare', + 'handleUpdateProjectId', 'handleUpdateProjectTitle', 'handleUpdate', 'handleToggleComments', @@ -66,7 +68,7 @@ class Preview extends React.Component { extensions: [], favoriteCount: 0, loveCount: 0, - projectId: parts[1] === 'editor' ? 0 : parts[1], + projectId: parts[1] === 'editor' ? '0' : parts[1], addToStudioOpen: false, reportOpen: false }; @@ -75,38 +77,30 @@ class Preview extends React.Component { /* In the beginning, if user is on mobile and landscape, go to fullscreen */ this.setScreenFromOrientation(); } - componentDidUpdate (prevProps) { - if (this.props.sessionStatus !== prevProps.sessionStatus && - this.props.sessionStatus === sessionActions.Status.FETCHED && - this.state.projectId) { - if (this.props.user) { - const username = this.props.user.username; - const token = this.props.user.token; - this.props.getTopLevelComments(this.state.projectId, this.props.comments.length, - this.props.isAdmin, token); - this.props.getProjectInfo(this.state.projectId, token); - this.props.getRemixes(this.state.projectId, token); - this.props.getProjectStudios(this.state.projectId, token); - this.props.getCuratedStudios(username); - this.props.getFavedStatus(this.state.projectId, username, token); - this.props.getLovedStatus(this.state.projectId, username, token); - } else { - this.props.getTopLevelComments(this.state.projectId, this.props.comments.length); - this.props.getProjectInfo(this.state.projectId); - this.props.getRemixes(this.state.projectId); - this.props.getProjectStudios(this.state.projectId); - } + componentDidUpdate (prevProps, prevState) { + if (this.state.projectId > 0 && + ((this.props.sessionStatus !== prevProps.sessionStatus && + this.props.sessionStatus === sessionActions.Status.FETCHED) || + (this.state.projectId !== prevState.projectId))) { + this.fetchCommunityData(); + } + if (this.state.projectId === '0' && this.state.projectId !== prevState.projectId) { + this.props.resetProject(); } if (this.props.projectInfo.id !== prevProps.projectInfo.id) { this.getExtensions(this.state.projectId); - this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves); - if (this.props.projectInfo.remix.parent !== null) { - this.props.getParentInfo(this.props.projectInfo.remix.parent); - } - if (this.props.projectInfo.remix.root !== null && - this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent - ) { - this.props.getOriginalInfo(this.props.projectInfo.remix.root); + if (typeof this.props.projectInfo.id === 'undefined') { + this.initCounts(0, 0); + } else { + this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves); + if (this.props.projectInfo.remix.parent !== null) { + this.props.getParentInfo(this.props.projectInfo.remix.parent); + } + if (this.props.projectInfo.remix.root !== null && + this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent + ) { + this.props.getOriginalInfo(this.props.projectInfo.remix.root); + } } } if (this.props.playerMode !== prevProps.playerMode || this.props.fullScreen !== prevProps.fullScreen) { @@ -124,6 +118,25 @@ class Preview extends React.Component { window.removeEventListener('popstate', this.handlePopState); window.removeEventListener('orientationchange', this.setScreenFromOrientation); } + fetchCommunityData () { + if (this.props.userPresent) { + const username = this.props.user.username; + const token = this.props.user.token; + this.props.getTopLevelComments(this.state.projectId, this.props.comments.length, + this.props.isAdmin, token); + this.props.getProjectInfo(this.state.projectId, token); + this.props.getRemixes(this.state.projectId, token); + this.props.getProjectStudios(this.state.projectId, token); + this.props.getCuratedStudios(username); + this.props.getFavedStatus(this.state.projectId, username, token); + this.props.getLovedStatus(this.state.projectId, username, token); + } else { + this.props.getTopLevelComments(this.state.projectId, this.props.comments.length); + this.props.getProjectInfo(this.state.projectId); + this.props.getRemixes(this.state.projectId); + this.props.getProjectStudios(this.state.projectId); + } + } setScreenFromOrientation () { /* * If the user is on a mobile device, switching to @@ -141,36 +154,42 @@ class Preview extends React.Component { } } getExtensions (projectId) { - storage - .load(storage.AssetType.Project, projectId, storage.DataFormat.JSON) - .then(projectAsset => { // NOTE: this is turning up null, breaking the line below. - let input = projectAsset.data; - if (typeof input === 'object' && !(input instanceof ArrayBuffer) && - !ArrayBuffer.isView(input)) { // taken from scratch-vm - // If the input is an object and not any ArrayBuffer - // or an ArrayBuffer view (this includes all typed arrays and DataViews) - // turn the object into a JSON string, because we suspect - // this is a project.json as an object - // validate expects a string or buffer as input - // TODO not sure if we need to check that it also isn't a data view - input = JSON.stringify(input); - } - parser(projectAsset.data, false, (err, projectData) => { - if (err) { - log.error(`Unhandled project parsing error: ${err}`); - return; + if (projectId > 0) { + storage + .load(storage.AssetType.Project, projectId, storage.DataFormat.JSON) + .then(projectAsset => { // NOTE: this is turning up null, breaking the line below. + let input = projectAsset.data; + if (typeof input === 'object' && !(input instanceof ArrayBuffer) && + !ArrayBuffer.isView(input)) { // taken from scratch-vm + // If the input is an object and not any ArrayBuffer + // or an ArrayBuffer view (this includes all typed arrays and DataViews) + // turn the object into a JSON string, because we suspect + // this is a project.json as an object + // validate expects a string or buffer as input + // TODO not sure if we need to check that it also isn't a data view + input = JSON.stringify(input); } - const extensionSet = new Set(); - if (projectData[0].extensions) { - projectData[0].extensions.forEach(extension => { - extensionSet.add(EXTENSION_INFO[extension]); + parser(projectAsset.data, false, (err, projectData) => { + if (err) { + log.error(`Unhandled project parsing error: ${err}`); + return; + } + const extensionSet = new Set(); + if (projectData[0].extensions) { + projectData[0].extensions.forEach(extension => { + extensionSet.add(EXTENSION_INFO[extension]); + }); + } + this.setState({ + extensions: Array.from(extensionSet) }); - } - this.setState({ - extensions: Array.from(extensionSet) }); }); + } else { // projectId is default or invalid; empty the extensions array + this.setState({ + extensions: [] }); + } } handleToggleComments () { this.props.updateProject( @@ -313,6 +332,25 @@ class Preview extends React.Component { title: title }); } + handleUpdateProjectId (projectId, callback) { + this.setState({projectId: projectId}, () => { + const parts = window.location.pathname.toLowerCase() + .split('/') + .filter(Boolean); + let newUrl; + if (projectId === '0') { + newUrl = `/${parts[0]}/editor`; + } else { + newUrl = `/${parts[0]}/${projectId}/editor`; + } + history.pushState( + {projectId: projectId}, + {projectId: projectId}, + newUrl + ); + if (callback) callback(); + }); + } initCounts (favorites, loves) { this.setState({ favoriteCount: favorites, @@ -389,7 +427,6 @@ class Preview extends React.Component { : @@ -432,6 +471,7 @@ Preview.propTypes = { canSaveAsCopy: PropTypes.bool, canShare: PropTypes.bool, comments: PropTypes.arrayOf(PropTypes.object), + enableCommunity: PropTypes.bool, faved: PropTypes.bool, fullScreen: PropTypes.bool, getCuratedStudios: PropTypes.func.isRequired, @@ -465,6 +505,7 @@ Preview.propTypes = { remixes: PropTypes.arrayOf(PropTypes.object), replies: PropTypes.objectOf(PropTypes.array), reportProject: PropTypes.func, + resetProject: PropTypes.func, sessionStatus: PropTypes.string, setFavedStatus: PropTypes.func.isRequired, setFullScreen: PropTypes.func.isRequired, @@ -482,7 +523,8 @@ Preview.propTypes = { email: PropTypes.string, classroomId: PropTypes.string }), - userOwnsProject: PropTypes.bool + userOwnsProject: PropTypes.bool, + userPresent: PropTypes.bool }; Preview.defaultProps = { @@ -493,12 +535,14 @@ Preview.defaultProps = { }, projectHost: process.env.PROJECT_HOST, sessionStatus: sessionActions.Status.NOT_FETCHED, - user: {} + user: {}, + userPresent: false }; const mapStateToProps = state => { const projectInfoPresent = Object.keys(state.preview.projectInfo).length > 0; - const userPresent = state.session.session.user && + const userPresent = state.session.session.user !== null && + typeof state.session.session.user !== 'undefined' && Object.keys(state.session.session.user).length > 0; const isLoggedIn = state.session.status === sessionActions.Status.FETCHED && userPresent; @@ -510,13 +554,14 @@ const mapStateToProps = state => { return { canAddToStudio: isLoggedIn && userOwnsProject, - canCreateNew: false, + canCreateNew: true, canRemix: false, canReport: isLoggedIn && !userOwnsProject, - canSave: userOwnsProject, + canSave: isLoggedIn && (userOwnsProject || !state.preview.projectInfo.id), canSaveAsCopy: false, canShare: userOwnsProject && state.permissions.social, comments: state.preview.comments, + enableCommunity: state.preview.projectInfo && state.preview.projectInfo.id > 0, faved: state.preview.faved, fullScreen: state.scratchGui.mode.isFullScreen, // project is editable iff logged in user is the author of the project, or @@ -538,7 +583,8 @@ const mapStateToProps = state => { replies: state.preview.replies, sessionStatus: state.session.status, // check if used user: state.session.session.user, - userOwnsProject: userOwnsProject + userOwnsProject: userOwnsProject, + userPresent: userPresent }; }; @@ -613,6 +659,9 @@ const mapDispatchToProps = dispatch => ({ reportProject: (id, formData, token) => { dispatch(previewActions.reportProject(id, formData, token)); }, + resetProject: () => { + dispatch(previewActions.resetProject()); + }, setOriginalInfo: info => { dispatch(previewActions.setOriginalInfo(info)); },