Initial GUI embed (#1869)

- `/preview/editor` will load GUI with an empty project
- `/preview/:id/editor` will load GUI with a project from the projects server (not local data)
- passes intl object to GUI
- `/preview/:id` with show the project page with an embedded player
- `/preview/:id/fullscreen` will load the project page with the player in fullscreen mode.

* Note that we needed to Increase memory for the build to avoid running out of heap space, and build time increases by about 2 minutes
This commit is contained in:
chrisgarrity 2018-05-02 15:27:49 -04:00 committed by GitHub
parent 04e27ec02a
commit d1420862ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 64 deletions

View file

@ -1,10 +1,10 @@
ESLINT=./node_modules/.bin/eslint ESLINT=./node_modules/.bin/eslint
NODE=node NODE= NODE_OPTIONS=--max_old_space_size=8000 node
SASSLINT=./node_modules/.bin/sass-lint -v SASSLINT=./node_modules/.bin/sass-lint -v
S3CMD=s3cmd sync -P --delete-removed --add-header=Cache-Control:no-cache,public,max-age=3600 S3CMD=s3cmd sync -P --delete-removed --add-header=Cache-Control:no-cache,public,max-age=3600
TAP=./node_modules/.bin/tap TAP=./node_modules/.bin/tap
WATCH=./node_modules/.bin/watch WATCH= NODE_OPTIONS=--max_old_space_size=8000 ./node_modules/.bin/watch
WEBPACK=./node_modules/.bin/webpack WEBPACK= NODE_OPTIONS=--max_old_space_size=8000 ./node_modules/.bin/webpack
# ------------------------------------ # ------------------------------------

View file

@ -94,6 +94,7 @@
"redux-thunk": "2.0.1", "redux-thunk": "2.0.1",
"sass-lint": "1.5.1", "sass-lint": "1.5.1",
"sass-loader": "6.0.6", "sass-loader": "6.0.6",
"scratch-gui": "0.1.0-prerelease.20180427201459",
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
"slick-carousel": "1.6.0", "slick-carousel": "1.6.0",
"source-map-support": "0.3.2", "source-map-support": "0.3.2",

View file

@ -193,7 +193,7 @@
}, },
{ {
"name": "preview", "name": "preview",
"pattern": "^/preview/?(\\d+)?/?$", "pattern": "^/preview/(*)/?$",
"routeAlias": "/preview/?$", "routeAlias": "/preview/?$",
"view": "preview/preview", "view": "preview/preview",
"title": "Scratch 3.0 Preview" "title": "Scratch 3.0 Preview"

View file

@ -6,12 +6,14 @@ const React = require('react');
const Formsy = require('formsy-react').default; const Formsy = require('formsy-react').default;
const classNames = require('classnames'); const classNames = require('classnames');
const GUI = require('scratch-gui').default;
const IntlGUI = injectIntl(GUI);
const sessionActions = require('../../redux/session.js'); const sessionActions = require('../../redux/session.js');
const decorateText = require('../../lib/decorate-text.jsx'); const decorateText = require('../../lib/decorate-text.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx'); const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Avatar = require('../../components/avatar/avatar.jsx'); const Avatar = require('../../components/avatar/avatar.jsx');
const CappedNumber = require('../../components/cappednumber/cappednumber.jsx'); const CappedNumber = require('../../components/cappednumber/cappednumber.jsx');
const placeholder = require('./gui-placeholder.png');
const ShareBanner = require('../../components/share-banner/share-banner.jsx'); const ShareBanner = require('../../components/share-banner/share-banner.jsx');
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx'); const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
const InplaceInput = require('../../components/forms/inplace-input.jsx'); const InplaceInput = require('../../components/forms/inplace-input.jsx');
@ -25,8 +27,10 @@ const PreviewPresentation = props => {
faved, faved,
favoriteCount, favoriteCount,
intl, intl,
isFullScreen,
loved, loved,
loveCount, loveCount,
projectId,
projectInfo, projectInfo,
remixes, remixes,
sessionStatus, sessionStatus,
@ -34,6 +38,7 @@ const PreviewPresentation = props => {
user, user,
onFavoriteClicked, onFavoriteClicked,
onLoveClicked, onLoveClicked,
onSeeInside,
onUpdate onUpdate
// ...otherProps TBD // ...otherProps TBD
} = props; } = props;
@ -93,15 +98,23 @@ const PreviewPresentation = props => {
Remix Remix
</button> </button>
} }
<button className="button see-inside-button"> <button
className="button see-inside-button"
onClick={onSeeInside}
>
See Inside See Inside
</button> </button>
</div> </div>
</FlexRow> </FlexRow>
<FlexRow className="preview-row"> <FlexRow className="preview-row">
<div className="placeholder"> <IntlGUI
<img src={placeholder} /> isPlayerOnly
</div> basePath="/"
className="guiPlayer"
isFullScreen={isFullScreen}
previewInfoVisible="false"
projectId={projectId}
/>
<FlexRow className="project-notes"> <FlexRow className="project-notes">
{shareDate && ( {shareDate && (
<div className="share-date"> <div className="share-date">
@ -286,11 +299,14 @@ PreviewPresentation.propTypes = {
faved: PropTypes.bool, faved: PropTypes.bool,
favoriteCount: PropTypes.number, favoriteCount: PropTypes.number,
intl: intlShape, intl: intlShape,
isFullScreen: PropTypes.bool,
loveCount: PropTypes.number, loveCount: PropTypes.number,
loved: PropTypes.bool, loved: PropTypes.bool,
onFavoriteClicked: PropTypes.func, onFavoriteClicked: PropTypes.func,
onLoveClicked: PropTypes.func, onLoveClicked: PropTypes.func,
onSeeInside: PropTypes.func,
onUpdate: PropTypes.func, onUpdate: PropTypes.func,
projectId: PropTypes.number,
projectInfo: PropTypes.shape({ projectInfo: PropTypes.shape({
id: PropTypes.number, id: PropTypes.number,
title: PropTypes.string, title: PropTypes.string,

View file

@ -2,6 +2,7 @@ const bindAll = require('lodash.bindall');
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const connect = require('react-redux').connect; const connect = require('react-redux').connect;
const injectIntl = require('react-intl').injectIntl;
const Page = require('../../components/page/www/page.jsx'); const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx'); const render = require('../../lib/render.jsx');
@ -9,6 +10,8 @@ const PreviewPresentation = require('./presentation.jsx');
const sessionActions = require('../../redux/session.js'); const sessionActions = require('../../redux/session.js');
const previewActions = require('../../redux/preview.js'); const previewActions = require('../../redux/preview.js');
const GUI = require('scratch-gui').default;
const IntlGUI = injectIntl(GUI);
class Preview extends React.Component { class Preview extends React.Component {
constructor (props) { constructor (props) {
@ -17,36 +20,28 @@ class Preview extends React.Component {
'handleFavoriteToggle', 'handleFavoriteToggle',
'handleLoveToggle', 'handleLoveToggle',
'handlePermissions', 'handlePermissions',
'handleSeeInside',
'handleUpdate', 'handleUpdate',
'initCounts' 'initCounts'
]); ]);
this.state = { this.state = this.initState();
editable: false,
favoriteCount: 0,
loveCount: 0
};
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
let pathname = window.location.pathname.toLowerCase();
if (pathname[pathname.length - 1] === '/') {
pathname = pathname.substring(0, pathname.length - 1);
}
const path = pathname.split('/');
const projectId = path[path.length - 1];
if (this.props.sessionStatus !== prevProps.sessionStatus && if (this.props.sessionStatus !== prevProps.sessionStatus &&
this.props.sessionStatus === sessionActions.Status.FETCHED) { this.props.sessionStatus === sessionActions.Status.FETCHED &&
this.state.projectId) {
if (this.props.user) { if (this.props.user) {
const username = this.props.user.username; const username = this.props.user.username;
const token = this.props.user.token; const token = this.props.user.token;
this.props.getProjectInfo(projectId, token); this.props.getProjectInfo(this.state.projectId, token);
this.props.getRemixes(projectId, token); this.props.getRemixes(this.state.projectId, token);
this.props.getStudios(projectId, token); this.props.getStudios(this.state.projectId, token);
this.props.getFavedStatus(projectId, username, token); this.props.getFavedStatus(this.state.projectId, username, token);
this.props.getLovedStatus(projectId, username, token); this.props.getLovedStatus(this.state.projectId, username, token);
} else { } else {
this.props.getProjectInfo(projectId); this.props.getProjectInfo(this.state.projectId);
this.props.getRemixes(projectId); this.props.getRemixes(this.state.projectId);
this.props.getStudios(projectId); this.props.getStudios(this.state.projectId);
} }
} }
@ -58,6 +53,33 @@ class Preview extends React.Component {
} }
} }
} }
initState () {
let pathname = window.location.pathname.toLowerCase();
const params = {
editable: false,
favoriteCount: 0,
inEditor: false,
isFullScreen: false,
loveCount: 0,
projectId: null
};
if (pathname[pathname.length - 1] === '/') {
pathname = pathname.substring(0, pathname.length - 1);
}
const path = pathname.split('/');
if (path[path.length - 1] === 'editor' || path[path.length - 1] === 'preview') {
params.inEditor = true;
path.pop();
}
if (path[path.length - 1] === 'fullscreen') {
params.isFullScreen = true;
path.pop();
}
if (/^\d+$/.test(path[path.length - 1])) {
params.projectId = path.pop();
}
return params;
}
handleFavoriteToggle () { handleFavoriteToggle () {
this.props.setFavedStatus( this.props.setFavedStatus(
!this.props.faved, !this.props.faved,
@ -98,6 +120,10 @@ class Preview extends React.Component {
this.setState({editable: true}); this.setState({editable: true});
} }
} }
handleSeeInside () {
this.setState({inEditor: true});
history.pushState({}, document.title, `/preview/${this.state.projectId}/editor`);
}
handleUpdate (jsonData) { handleUpdate (jsonData) {
this.props.updateProject( this.props.updateProject(
this.props.projectInfo.id, this.props.projectInfo.id,
@ -114,14 +140,24 @@ class Preview extends React.Component {
} }
render () { render () {
return ( return (
this.state.inEditor ?
<IntlGUI
basePath="/"
className="gui"
isPlayerOnly={false}
projectId={this.state.projectId}
/> :
<Page>
<PreviewPresentation <PreviewPresentation
comments={this.props.comments} comments={this.props.comments}
creditInfo={this.props.credit} creditInfo={this.props.credit}
editable={this.state.editable} editable={this.state.editable}
faved={this.props.faved} faved={this.props.faved}
favoriteCount={this.state.favoriteCount} favoriteCount={this.state.favoriteCount}
isFullScreen={this.state.isFullScreen}
loveCount={this.state.loveCount} loveCount={this.state.loveCount}
loved={this.props.loved} loved={this.props.loved}
projectId={this.state.projectId}
projectInfo={this.props.projectInfo} projectInfo={this.props.projectInfo}
remixes={this.props.remixes} remixes={this.props.remixes}
sessionStatus={this.props.sessionStatus} sessionStatus={this.props.sessionStatus}
@ -129,8 +165,10 @@ class Preview extends React.Component {
user={this.props.user} user={this.props.user}
onFavoriteClicked={this.handleFavoriteToggle} onFavoriteClicked={this.handleFavoriteToggle}
onLoveClicked={this.handleLoveToggle} onLoveClicked={this.handleLoveToggle}
onSeeInside={this.handleSeeInside}
onUpdate={this.handleUpdate} onUpdate={this.handleUpdate}
/> />
</Page>
); );
} }
} }
@ -268,7 +306,7 @@ const ConnectedPreview = connect(
)(Preview); )(Preview);
render( render(
<Page><ConnectedPreview /></Page>, <ConnectedPreview />,
document.getElementById('app'), document.getElementById('app'),
{preview: previewActions.previewReducer} {preview: previewActions.previewReducer}
); );

View file

@ -1,21 +1,21 @@
@import "../../colors"; @import "../../colors";
@import "../../frameless"; @import "../../frameless";
html,
body,
#app,
.gui {
margin: 0;
width: 100%;
height: 100%;
}
/* override view padding for share banner */ /* override view padding for share banner */
#view { #view {
padding: 0 0 20px 0; padding: 0 0 20px 0;
} }
.gui * { box-sizing: border-box; } .gui {
position: absolute;
top: 0;
z-index: 11;
margin: 0;
width: 100%;
height: 100%;
}
// .gui * { box-sizing: border-box; }
.preview { .preview {
@ -162,15 +162,11 @@ body,
align-items: flex-start; align-items: flex-start;
} }
.placeholder { .guiPlayer {
display: inline-block; display: inline-block;
width: 480px; width: 480px;
} }
.placeholder img {
width: 100%;
}
.project-notes { .project-notes {
width: 45%; width: 45%;
height: 404px; height: 404px;

View file

@ -142,7 +142,21 @@ module.exports = {
new CopyWebpackPlugin([ new CopyWebpackPlugin([
{from: 'static'}, {from: 'static'},
{from: 'intl', to: 'js'} {from: 'intl', to: 'js'}
]) ]),
new CopyWebpackPlugin([{
from: 'node_modules/scratch-gui/dist/static/blocks-media',
to: 'static/blocks-media'
}]),
new CopyWebpackPlugin([{
from: 'node_modules/scratch-gui/dist/extension-worker.js'
}]),
new CopyWebpackPlugin([{
from: 'node_modules/scratch-gui/dist/extension-worker.js.map'
}]),
new CopyWebpackPlugin([{
from: 'node_modules/scratch-gui/dist/static/assets',
to: 'static/assets'
}])
]) ])
.concat(process.env.NODE_ENV === 'production' ? [ .concat(process.env.NODE_ENV === 'production' ? [
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({