From 77095807105dc86a8565a7530d88bf12f7f25eaf Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 18 Mar 2019 12:00:01 -0400 Subject: [PATCH] Move project saving to server to helper, allow external save action --- src/lib/project-saver-hoc.jsx | 61 ++++++++---------------------- src/lib/save-project-to-server.js | 62 +++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 45 deletions(-) create mode 100644 src/lib/save-project-to-server.js diff --git a/src/lib/project-saver-hoc.jsx b/src/lib/project-saver-hoc.jsx index 2cee625ec..846cfbe8b 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 => { @@ -234,45 +233,14 @@ const ProjectSaverHOC = function (WrappedComponent) { ) ) ).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}` - }); + const {onUpdateProjectData} = this.props; + const saveReturnValue = onUpdateProjectData(projectId, + savedVMState, requestParams); + // Allow onUpdateProjectData to either return immediately or a promise. + if (!(saveReturnValue.then && saveReturnValue.catch)) { + return Promise.resolve(saveReturnValue); } - 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); - }); - }); + return saveReturnValue; }) .then(response => { this.props.onSetProjectUnchanged(); @@ -358,6 +326,7 @@ const ProjectSaverHOC = function (WrappedComponent) { onShowSaveSuccessAlert, onShowSavingAlert, onUpdatedProject, + onUpdateProjectData, onUpdateProjectThumbnail, reduxProjectId, reduxProjectTitle, @@ -403,6 +372,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 +383,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); + }); + }); +}