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) {
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

View file

@ -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 {
</Page> :
<React.Fragment>
<IntlGUI
enableCommunity
hideIntro
assetHost={this.props.assetHost}
backpackOptions={this.props.backpackOptions}
@ -400,6 +437,7 @@ class Preview extends React.Component {
canSaveAsCopy={this.props.canSaveAsCopy}
canShare={this.props.canShare}
className="gui"
enableCommunity={this.props.enableCommunity}
projectHost={this.props.projectHost}
projectId={this.state.projectId}
projectTitle={this.props.projectInfo.title}
@ -408,6 +446,7 @@ class Preview extends React.Component {
onOpenRegistration={this.props.handleOpenRegistration}
onShare={this.handleShare}
onToggleLoginOpen={this.props.handleToggleLoginOpen}
onUpdateProjectId={this.handleUpdateProjectId}
onUpdateProjectTitle={this.handleUpdateProjectTitle}
/>
<Registration />
@ -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));
},