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
This commit is contained in:
Benjamin Wheeler 2018-10-19 16:02:59 -04:00 committed by GitHub
parent 8acbf05b1a
commit a6409bbcce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 62 deletions

View file

@ -46,6 +46,8 @@ module.exports.previewReducer = (state, action) => {
} }
switch (action.type) { switch (action.type) {
case 'RESET_TO_INTIAL_STATE':
return module.exports.getInitialState();
case 'SET_PROJECT_INFO': case 'SET_PROJECT_INFO':
return Object.assign({}, state, { return Object.assign({}, state, {
projectInfo: action.info projectInfo: action.info
@ -164,6 +166,10 @@ module.exports.setError = error => ({
error: error error: error
}); });
module.exports.resetProject = () => ({
type: 'RESET_TO_INTIAL_STATE'
});
module.exports.setProjectInfo = info => ({ module.exports.setProjectInfo = info => ({
type: 'SET_PROJECT_INFO', type: 'SET_PROJECT_INFO',
info: info info: info

View file

@ -33,6 +33,7 @@ class Preview extends React.Component {
super(props); super(props);
bindAll(this, [ bindAll(this, [
'addEventListeners', 'addEventListeners',
'fetchCommunityData',
'handleAddComment', 'handleAddComment',
'handleDeleteComment', 'handleDeleteComment',
'handleToggleStudio', 'handleToggleStudio',
@ -49,6 +50,7 @@ class Preview extends React.Component {
'handleAddToStudioClose', 'handleAddToStudioClose',
'handleSeeInside', 'handleSeeInside',
'handleShare', 'handleShare',
'handleUpdateProjectId',
'handleUpdateProjectTitle', 'handleUpdateProjectTitle',
'handleUpdate', 'handleUpdate',
'handleToggleComments', 'handleToggleComments',
@ -66,7 +68,7 @@ class Preview extends React.Component {
extensions: [], extensions: [],
favoriteCount: 0, favoriteCount: 0,
loveCount: 0, loveCount: 0,
projectId: parts[1] === 'editor' ? 0 : parts[1], projectId: parts[1] === 'editor' ? '0' : parts[1],
addToStudioOpen: false, addToStudioOpen: false,
reportOpen: 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 */ /* In the beginning, if user is on mobile and landscape, go to fullscreen */
this.setScreenFromOrientation(); this.setScreenFromOrientation();
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps, prevState) {
if (this.props.sessionStatus !== prevProps.sessionStatus && if (this.state.projectId > 0 &&
this.props.sessionStatus === sessionActions.Status.FETCHED && ((this.props.sessionStatus !== prevProps.sessionStatus &&
this.state.projectId) { this.props.sessionStatus === sessionActions.Status.FETCHED) ||
if (this.props.user) { (this.state.projectId !== prevState.projectId))) {
const username = this.props.user.username; this.fetchCommunityData();
const token = this.props.user.token; }
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length, if (this.state.projectId === '0' && this.state.projectId !== prevState.projectId) {
this.props.isAdmin, token); this.props.resetProject();
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);
}
} }
if (this.props.projectInfo.id !== prevProps.projectInfo.id) { if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
this.getExtensions(this.state.projectId); this.getExtensions(this.state.projectId);
this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves); if (typeof this.props.projectInfo.id === 'undefined') {
if (this.props.projectInfo.remix.parent !== null) { this.initCounts(0, 0);
this.props.getParentInfo(this.props.projectInfo.remix.parent); } else {
} this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves);
if (this.props.projectInfo.remix.root !== null && if (this.props.projectInfo.remix.parent !== null) {
this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent this.props.getParentInfo(this.props.projectInfo.remix.parent);
) { }
this.props.getOriginalInfo(this.props.projectInfo.remix.root); 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) { 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('popstate', this.handlePopState);
window.removeEventListener('orientationchange', this.setScreenFromOrientation); 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 () { setScreenFromOrientation () {
/* /*
* If the user is on a mobile device, switching to * If the user is on a mobile device, switching to
@ -141,36 +154,42 @@ class Preview extends React.Component {
} }
} }
getExtensions (projectId) { getExtensions (projectId) {
storage if (projectId > 0) {
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON) storage
.then(projectAsset => { // NOTE: this is turning up null, breaking the line below. .load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
let input = projectAsset.data; .then(projectAsset => { // NOTE: this is turning up null, breaking the line below.
if (typeof input === 'object' && !(input instanceof ArrayBuffer) && let input = projectAsset.data;
!ArrayBuffer.isView(input)) { // taken from scratch-vm if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
// If the input is an object and not any ArrayBuffer !ArrayBuffer.isView(input)) { // taken from scratch-vm
// or an ArrayBuffer view (this includes all typed arrays and DataViews) // If the input is an object and not any ArrayBuffer
// turn the object into a JSON string, because we suspect // or an ArrayBuffer view (this includes all typed arrays and DataViews)
// this is a project.json as an object // turn the object into a JSON string, because we suspect
// validate expects a string or buffer as input // this is a project.json as an object
// TODO not sure if we need to check that it also isn't a data view // validate expects a string or buffer as input
input = JSON.stringify(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;
} }
const extensionSet = new Set(); parser(projectAsset.data, false, (err, projectData) => {
if (projectData[0].extensions) { if (err) {
projectData[0].extensions.forEach(extension => { log.error(`Unhandled project parsing error: ${err}`);
extensionSet.add(EXTENSION_INFO[extension]); 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 () { handleToggleComments () {
this.props.updateProject( this.props.updateProject(
@ -313,6 +332,25 @@ class Preview extends React.Component {
title: title 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) { initCounts (favorites, loves) {
this.setState({ this.setState({
favoriteCount: favorites, favoriteCount: favorites,
@ -389,7 +427,6 @@ class Preview extends React.Component {
</Page> : </Page> :
<React.Fragment> <React.Fragment>
<IntlGUI <IntlGUI
enableCommunity
hideIntro hideIntro
assetHost={this.props.assetHost} assetHost={this.props.assetHost}
backpackOptions={this.props.backpackOptions} backpackOptions={this.props.backpackOptions}
@ -400,6 +437,7 @@ class Preview extends React.Component {
canSaveAsCopy={this.props.canSaveAsCopy} canSaveAsCopy={this.props.canSaveAsCopy}
canShare={this.props.canShare} canShare={this.props.canShare}
className="gui" className="gui"
enableCommunity={this.props.enableCommunity}
projectHost={this.props.projectHost} projectHost={this.props.projectHost}
projectId={this.state.projectId} projectId={this.state.projectId}
projectTitle={this.props.projectInfo.title} projectTitle={this.props.projectInfo.title}
@ -408,6 +446,7 @@ class Preview extends React.Component {
onOpenRegistration={this.props.handleOpenRegistration} onOpenRegistration={this.props.handleOpenRegistration}
onShare={this.handleShare} onShare={this.handleShare}
onToggleLoginOpen={this.props.handleToggleLoginOpen} onToggleLoginOpen={this.props.handleToggleLoginOpen}
onUpdateProjectId={this.handleUpdateProjectId}
onUpdateProjectTitle={this.handleUpdateProjectTitle} onUpdateProjectTitle={this.handleUpdateProjectTitle}
/> />
<Registration /> <Registration />
@ -432,6 +471,7 @@ Preview.propTypes = {
canSaveAsCopy: PropTypes.bool, canSaveAsCopy: PropTypes.bool,
canShare: PropTypes.bool, canShare: PropTypes.bool,
comments: PropTypes.arrayOf(PropTypes.object), comments: PropTypes.arrayOf(PropTypes.object),
enableCommunity: PropTypes.bool,
faved: PropTypes.bool, faved: PropTypes.bool,
fullScreen: PropTypes.bool, fullScreen: PropTypes.bool,
getCuratedStudios: PropTypes.func.isRequired, getCuratedStudios: PropTypes.func.isRequired,
@ -465,6 +505,7 @@ Preview.propTypes = {
remixes: PropTypes.arrayOf(PropTypes.object), remixes: PropTypes.arrayOf(PropTypes.object),
replies: PropTypes.objectOf(PropTypes.array), replies: PropTypes.objectOf(PropTypes.array),
reportProject: PropTypes.func, reportProject: PropTypes.func,
resetProject: PropTypes.func,
sessionStatus: PropTypes.string, sessionStatus: PropTypes.string,
setFavedStatus: PropTypes.func.isRequired, setFavedStatus: PropTypes.func.isRequired,
setFullScreen: PropTypes.func.isRequired, setFullScreen: PropTypes.func.isRequired,
@ -482,7 +523,8 @@ Preview.propTypes = {
email: PropTypes.string, email: PropTypes.string,
classroomId: PropTypes.string classroomId: PropTypes.string
}), }),
userOwnsProject: PropTypes.bool userOwnsProject: PropTypes.bool,
userPresent: PropTypes.bool
}; };
Preview.defaultProps = { Preview.defaultProps = {
@ -493,12 +535,14 @@ Preview.defaultProps = {
}, },
projectHost: process.env.PROJECT_HOST, projectHost: process.env.PROJECT_HOST,
sessionStatus: sessionActions.Status.NOT_FETCHED, sessionStatus: sessionActions.Status.NOT_FETCHED,
user: {} user: {},
userPresent: false
}; };
const mapStateToProps = state => { const mapStateToProps = state => {
const projectInfoPresent = Object.keys(state.preview.projectInfo).length > 0; 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; Object.keys(state.session.session.user).length > 0;
const isLoggedIn = state.session.status === sessionActions.Status.FETCHED && const isLoggedIn = state.session.status === sessionActions.Status.FETCHED &&
userPresent; userPresent;
@ -510,13 +554,14 @@ const mapStateToProps = state => {
return { return {
canAddToStudio: isLoggedIn && userOwnsProject, canAddToStudio: isLoggedIn && userOwnsProject,
canCreateNew: false, canCreateNew: true,
canRemix: false, canRemix: false,
canReport: isLoggedIn && !userOwnsProject, canReport: isLoggedIn && !userOwnsProject,
canSave: userOwnsProject, canSave: isLoggedIn && (userOwnsProject || !state.preview.projectInfo.id),
canSaveAsCopy: false, canSaveAsCopy: false,
canShare: userOwnsProject && state.permissions.social, canShare: userOwnsProject && state.permissions.social,
comments: state.preview.comments, comments: state.preview.comments,
enableCommunity: state.preview.projectInfo && state.preview.projectInfo.id > 0,
faved: state.preview.faved, faved: state.preview.faved,
fullScreen: state.scratchGui.mode.isFullScreen, fullScreen: state.scratchGui.mode.isFullScreen,
// project is editable iff logged in user is the author of the project, or // 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, replies: state.preview.replies,
sessionStatus: state.session.status, // check if used sessionStatus: state.session.status, // check if used
user: state.session.session.user, user: state.session.session.user,
userOwnsProject: userOwnsProject userOwnsProject: userOwnsProject,
userPresent: userPresent
}; };
}; };
@ -613,6 +659,9 @@ const mapDispatchToProps = dispatch => ({
reportProject: (id, formData, token) => { reportProject: (id, formData, token) => {
dispatch(previewActions.reportProject(id, formData, token)); dispatch(previewActions.reportProject(id, formData, token));
}, },
resetProject: () => {
dispatch(previewActions.resetProject());
},
setOriginalInfo: info => { setOriginalInfo: info => {
dispatch(previewActions.setOriginalInfo(info)); dispatch(previewActions.setOriginalInfo(info));
}, },