diff --git a/package-lock.json b/package-lock.json index f09de5985..305f8501d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12032,6 +12032,24 @@ "prepend-http": "^1.0.0", "query-string": "^4.1.0", "sort-keys": "^1.0.0" + }, + "dependencies": { + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + } } }, "npm-run-path": { @@ -13907,11 +13925,12 @@ "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" }, "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", "dev": true, "requires": { + "decode-uri-component": "^0.2.0", "object-assign": "^4.1.0", "strict-uri-encode": "^1.0.0" } diff --git a/package.json b/package.json index 273993c99..fb3e7dad1 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "po2icu": "0.0.2", "postcss-loader": "2.0.10", "prop-types": "15.6.0", + "query-string": "^5.1.1", "react": "16.2.0", "react-dom": "16.2.0", "react-intl": "2.8.0", diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index bba60660e..bc423ec9e 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -111,6 +111,7 @@ const PreviewPresentation = ({ onSocialClosed, onToggleComments, onToggleStudio, + onUpdateProjectData, onUpdateProjectId, onUpdateProjectThumbnail, originalInfo, @@ -345,6 +346,7 @@ const PreviewPresentation = ({ onProjectLoaded={onProjectLoaded} onRemixing={onRemixing} onSetProjectThumbnailer={onSetProjectThumbnailer} + onUpdateProjectData={onUpdateProjectData} onUpdateProjectId={onUpdateProjectId} onUpdateProjectThumbnail={onUpdateProjectThumbnail} /> @@ -725,6 +727,7 @@ PreviewPresentation.propTypes = { onSocialClosed: PropTypes.func, onToggleComments: PropTypes.func, onToggleStudio: PropTypes.func, + onUpdateProjectData: PropTypes.func, onUpdateProjectId: PropTypes.func, onUpdateProjectThumbnail: PropTypes.func, originalInfo: projectShape, diff --git a/src/views/preview/project-view.jsx b/src/views/preview/project-view.jsx index c41ec84ee..b9471eeaf 100644 --- a/src/views/preview/project-view.jsx +++ b/src/views/preview/project-view.jsx @@ -8,7 +8,9 @@ const PropTypes = require('prop-types'); const connect = require('react-redux').connect; const injectIntl = require('react-intl').injectIntl; const parser = require('scratch-parser'); +const queryString = require('query-string'); +const api = require('../../lib/api'); const Page = require('../../components/page/www/page.jsx'); const storage = require('../../lib/storage.js').default; const log = require('../../lib/log'); @@ -36,6 +38,7 @@ const IntlGUI = injectIntl(GUI.default); const localStorageAvailable = 'localStorage' in window && window.localStorage !== null; const initSentry = require('../../lib/sentry.js'); +const xhr = require('xhr'); initSentry(); class Preview extends React.Component { @@ -73,6 +76,7 @@ class Preview extends React.Component { 'handleSeeInside', 'handleSetProjectThumbnailer', 'handleShare', + 'handleUpdateProjectData', 'handleUpdateProjectId', 'handleUpdateProjectTitle', 'handleToggleComments', @@ -219,6 +223,84 @@ class Preview extends React.Component { this.props.getRemixes(this.state.projectId); } } + + // This is copy of what is in save-project-to-server in GUI that adds + // an extra get of the project info from api. We do this to wait for replication + // lag to pass. This is intended to be a temporary fix until we use the data + // from the create request to fill the projectInfo state. + handleUpdateProjectData (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: `${this.props.projectHost}/${qs}` + }); + } else { + Object.assign(opts, { + method: 'put', + url: `${this.props.projectHost}/${projectId}${qs}` + }); + } + return new Promise((resolve, reject) => { + xhr(opts, (err, response) => { + if (err) return reject(err); + if (response.statusCode !== 200) return reject(response.statusCode); + 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(body => { + const fetchProjectInfo = (count, resolve) => { + api({ + uri: `/projects/${body.id}`, + authentication: this.props.user.token + }, (err, projectInfo, response) => { + if (err) { + log.error(`Could not fetch project after creating: ${err}`); + return resolve(body); + } + if (typeof body === 'undefined' || response.statusCode === 404) { + // Retry after 500ms, 1.5s, 3.5s, 7.5s and then stop. + if (count > 4) { + return resolve(body); + } + return setTimeout( + fetchProjectInfo.bind(this, count + 1, resolve), + 500 * Math.pow(2, count)); + } + return resolve(body); + }); + }; + if (creatingProject) { + return new Promise((resolve, reject) => fetchProjectInfo(1, resolve, reject)); + } + return body; + }); + } setScreenFromOrientation () { /* * If the user is on a mobile device, switching to @@ -702,6 +784,7 @@ class Preview extends React.Component { onSocialClosed={this.handleSocialClose} onToggleComments={this.handleToggleComments} onToggleStudio={this.handleToggleStudio} + onUpdateProjectData={this.handleUpdateProjectData} onUpdateProjectId={this.handleUpdateProjectId} onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail} /> @@ -739,6 +822,7 @@ class Preview extends React.Component { onSetLanguage={this.handleSetLanguage} onShare={this.handleShare} onToggleLoginOpen={this.props.handleToggleLoginOpen} + onUpdateProjectData={this.handleUpdateProjectData} onUpdateProjectId={this.handleUpdateProjectId} onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail} onUpdateProjectTitle={this.handleUpdateProjectTitle}