diff --git a/src/lib/project-saver-hoc.jsx b/src/lib/project-saver-hoc.jsx index 2cee625ec..911769762 100644 --- a/src/lib/project-saver-hoc.jsx +++ b/src/lib/project-saver-hoc.jsx @@ -1,15 +1,14 @@ import bindAll from 'lodash.bindall'; import React from 'react'; import PropTypes from 'prop-types'; -import queryString from 'query-string'; import {connect} from 'react-redux'; import VM from 'scratch-vm'; -import xhr from 'xhr'; import collectMetadata from '../lib/collect-metadata'; import log from '../lib/log'; import storage from '../lib/storage'; import dataURItoBlob from '../lib/data-uri-to-blob'; +import saveProjectToServer from '../lib/save-project-to-server'; import { showAlertWithTimeout, @@ -176,8 +175,8 @@ const ProjectSaverHOC = function (WrappedComponent) { createCopyToStorage () { this.props.onShowCreatingCopyAlert(); return this.storeProject(null, { - original_id: this.props.reduxProjectId, - is_copy: 1, + originalId: this.props.reduxProjectId, + isCopy: 1, title: this.props.reduxProjectTitle }) .then(response => { @@ -192,8 +191,8 @@ const ProjectSaverHOC = function (WrappedComponent) { createRemixToStorage () { this.props.onShowCreatingRemixAlert(); return this.storeProject(null, { - original_id: this.props.reduxProjectId, - is_remix: 1, + originalId: this.props.reduxProjectId, + isRemix: 1, title: this.props.reduxProjectTitle }) .then(response => { @@ -233,47 +232,8 @@ const ProjectSaverHOC = function (WrappedComponent) { () => (asset.clean = true) ) ) - ).then(() => { - const opts = { - body: savedVMState, - // If we set json:true then the body is double-stringified, so don't - headers: { - 'Content-Type': 'application/json' - }, - withCredentials: true - }; - const creatingProject = projectId === null || typeof projectId === 'undefined'; - let qs = queryString.stringify(requestParams); - if (qs) qs = `?${qs}`; - if (creatingProject) { - Object.assign(opts, { - method: 'post', - url: `${storage.projectHost}/${qs}` - }); - } else { - Object.assign(opts, { - method: 'put', - url: `${storage.projectHost}/${projectId}${qs}` - }); - } - return new Promise((resolve, reject) => { - xhr(opts, (err, response) => { - if (err) return reject(err); - let body; - try { - // Since we didn't set json: true, we have to parse manually - body = JSON.parse(response.body); - } catch (e) { - return reject(e); - } - body.id = projectId; - if (creatingProject) { - body.id = body['content-name']; - } - resolve(body); - }); - }); - }) + ) + .then(() => this.props.onUpdateProjectData(projectId, savedVMState, requestParams)) .then(response => { this.props.onSetProjectUnchanged(); const id = response.id.toString(); @@ -358,6 +318,7 @@ const ProjectSaverHOC = function (WrappedComponent) { onShowSaveSuccessAlert, onShowSavingAlert, onUpdatedProject, + onUpdateProjectData, onUpdateProjectThumbnail, reduxProjectId, reduxProjectTitle, @@ -403,6 +364,7 @@ const ProjectSaverHOC = function (WrappedComponent) { onShowRemixSuccessAlert: PropTypes.func, onShowSaveSuccessAlert: PropTypes.func, onShowSavingAlert: PropTypes.func, + onUpdateProjectData: PropTypes.func.isRequired, onUpdateProjectThumbnail: PropTypes.func, onUpdatedProject: PropTypes.func, projectChanged: PropTypes.bool, @@ -413,7 +375,8 @@ const ProjectSaverHOC = function (WrappedComponent) { ProjectSaverComponent.defaultProps = { autoSaveIntervalSecs: 120, onRemixing: () => {}, - onSetProjectThumbnailer: () => {} + onSetProjectThumbnailer: () => {}, + onUpdateProjectData: saveProjectToServer }; const mapStateToProps = (state, ownProps) => { const loadingState = state.scratchGui.projectState.loadingState; diff --git a/src/lib/save-project-to-server.js b/src/lib/save-project-to-server.js new file mode 100644 index 000000000..12301bbbd --- /dev/null +++ b/src/lib/save-project-to-server.js @@ -0,0 +1,62 @@ +import queryString from 'query-string'; +import xhr from 'xhr'; +import storage from '../lib/storage'; + +/** + * Save a project JSON to the project server. + * This should eventually live in scratch-www. + * @param {number} projectId the ID of the project, null if a new project. + * @param {object} vmState the JSON project representation. + * @param {object} params the request params. + * @property {?number} params.originalId the original project ID if a copy/remix. + * @property {?boolean} params.isCopy a flag indicating if this save is creating a copy. + * @property {?boolean} params.isRemix a flag indicating if this save is creating a remix. + * @property {?string} params.title the title of the project. + * @return {Promise} A promise that resolves when the network request resolves. + */ +export default function (projectId, vmState, params) { + const opts = { + body: vmState, + // If we set json:true then the body is double-stringified, so don't + headers: { + 'Content-Type': 'application/json' + }, + withCredentials: true + }; + const creatingProject = projectId === null || typeof projectId === 'undefined'; + const queryParams = {}; + if (params.hasOwnProperty('originalId')) queryParams.original_id = params.originalId; + if (params.hasOwnProperty('isCopy')) queryParams.is_copy = params.isCopy; + if (params.hasOwnProperty('isRemix')) queryParams.is_remix = params.isRemix; + if (params.hasOwnProperty('title')) queryParams.title = params.title; + let qs = queryString.stringify(queryParams); + if (qs) qs = `?${qs}`; + if (creatingProject) { + Object.assign(opts, { + method: 'post', + url: `${storage.projectHost}/${qs}` + }); + } else { + Object.assign(opts, { + method: 'put', + url: `${storage.projectHost}/${projectId}${qs}` + }); + } + return new Promise((resolve, reject) => { + xhr(opts, (err, response) => { + if (err) return reject(err); + let body; + try { + // Since we didn't set json: true, we have to parse manually + body = JSON.parse(response.body); + } catch (e) { + return reject(e); + } + body.id = projectId; + if (creatingProject) { + body.id = body['content-name']; + } + resolve(body); + }); + }); +}