Skip to content

Commit 2f5a964

Browse files
authored
Merge pull request #3303 from benjiwheeler/new-project
User can load new project in 3.0
2 parents 4219620 + 43ca154 commit 2f5a964

25 files changed

+1147
-439
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ npm start
2929
Then go to [http://localhost:8601/](http://localhost:8601/) - the playground outputs the default GUI component
3030

3131
## Developing alongside other Scratch repositories
32+
33+
### Linking this code to another project's `node_modules/scratch-gui`
34+
35+
#### Configuration
36+
3237
If you wish to develop scratch-gui alongside other scratch repositories that depend on it, you may wish
3338
to have the other repositories use your local scratch-gui build instead of fetching the current production
3439
version of the scratch-gui that is found by default using `npm install`.
@@ -43,7 +48,7 @@ To do this:
4348

4449
Instead of `BUILD_MODE=dist npm run build` you can also use `BUILD_MODE=dist npm run watch`, however this may be unreliable.
4550

46-
### Oh no! It didn't work!
51+
#### Oh no! It didn't work!
4752
* Follow the recipe above step by step and don't change the order. It is especially important to run npm first because installing after the linking will reset the linking.
4853
* Make sure the repositories are siblings on your machine's file tree.
4954
* If you have multiple Terminal tabs or windows open for the different Scratch repositories, make sure to use the same node version in all of them.

src/components/gui/gui.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,12 @@ $fade-out-distance: 15px;
279279
margin-top: 0;
280280
}
281281

282+
/* Menu */
283+
284+
.menu-bar-position {
285+
position: relative;
286+
z-index: $z-index-menu-bar;
287+
}
282288
/* Alerts */
283289

284290
.alerts-container {

src/components/gui/gui.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ const GUIComponent = props => {
164164
) : null}
165165
<MenuBar
166166
accountNavOpen={accountNavOpen}
167+
className={styles.menuBarPosition}
167168
enableCommunity={enableCommunity}
168169
renderLogin={renderLogin}
169170
onClickAccountNav={onClickAccountNav}

src/components/menu-bar/menu-bar.jsx

Lines changed: 119 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,24 @@ import Button from '../button/button.jsx';
1010
import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx';
1111
import Divider from '../divider/divider.jsx';
1212
import LanguageSelector from '../../containers/language-selector.jsx';
13-
import ProjectLoader from '../../containers/project-loader.jsx';
13+
import SBFileUploader from '../../containers/sb-file-uploader.jsx';
1414
import MenuBarMenu from './menu-bar-menu.jsx';
1515
import {MenuItem, MenuSection} from '../menu/menu.jsx';
1616
import ProjectTitleInput from './project-title-input.jsx';
1717
import AccountNav from '../../containers/account-nav.jsx';
1818
import LoginDropdown from './login-dropdown.jsx';
19-
import ProjectSaver from '../../containers/project-saver.jsx';
19+
import SB3Downloader from '../../containers/sb3-downloader.jsx';
2020
import DeletionRestorer from '../../containers/deletion-restorer.jsx';
2121
import TurboMode from '../../containers/turbo-mode.jsx';
2222

2323
import {openTipsLibrary} from '../../reducers/modals';
2424
import {setPlayer} from '../../reducers/mode';
25+
import {
26+
getIsUpdating,
27+
getIsShowingProject,
28+
requestNewProject,
29+
saveProject
30+
} from '../../reducers/project-state';
2531
import {
2632
openAccountMenu,
2733
closeAccountMenu,
@@ -123,42 +129,50 @@ class MenuBar extends React.Component {
123129
constructor (props) {
124130
super(props);
125131
bindAll(this, [
132+
'handleClickNew',
133+
'handleClickSave',
134+
'handleCloseFileMenuAndThen',
126135
'handleLanguageMouseUp',
127136
'handleRestoreOption',
128-
'handleCloseFileMenuAndThen',
129137
'restoreOptionMessage'
130138
]);
131-
this.state = {projectSaveInProgress: false};
132139
}
133-
handleLanguageMouseUp (e) {
134-
if (!this.props.languageMenuOpen) {
135-
this.props.onClickLanguage(e);
140+
componentDidUpdate (prevProps) {
141+
// if we're no longer showing the project (loading, or whatever), close menus
142+
if (this.props.isShowingProject && !prevProps.isShowingProject) {
143+
this.props.onRequestCloseFile();
144+
this.props.onRequestCloseEdit();
145+
}
146+
}
147+
handleClickNew () {
148+
const canSave = this.props.canUpdateProject; // logged in
149+
// if canSave===true, it's safe to replace current project, since we will auto-save first
150+
const readyToReplaceProject =
151+
canSave || confirm('Replace contents of the current project?'); // eslint-disable-line no-alert
152+
if (readyToReplaceProject) {
153+
this.props.onClickNew(canSave);
136154
}
137155
}
156+
handleClickSave () {
157+
this.props.onClickSave();
158+
}
138159
handleRestoreOption (restoreFun) {
139160
return () => {
140161
restoreFun();
141162
this.props.onRequestCloseEdit();
142163
};
143164
}
144-
handleUpdateProject (updateFun) {
145-
return () => {
146-
this.props.onRequestCloseFile();
147-
this.setState({projectSaveInProgress: true},
148-
() => {
149-
updateFun().then(() => {
150-
this.setState({projectSaveInProgress: false});
151-
});
152-
}
153-
);
154-
};
155-
}
156165
handleCloseFileMenuAndThen (fn) {
157166
return () => {
158167
this.props.onRequestCloseFile();
159168
fn();
160169
};
161170
}
171+
handleLanguageMouseUp (e) {
172+
if (!this.props.languageMenuOpen) {
173+
this.props.onClickLanguage(e);
174+
}
175+
}
162176
restoreOptionMessage (deletedItem) {
163177
switch (deletedItem) {
164178
case 'Sprite':
@@ -196,6 +210,13 @@ class MenuBar extends React.Component {
196210
id="gui.menuBar.saveNow"
197211
/>
198212
);
213+
const newProjectMessage = (
214+
<FormattedMessage
215+
defaultMessage="New"
216+
description="Menu bar item for creating a new project"
217+
id="gui.menuBar.new"
218+
/>
219+
);
199220
const shareButton = (
200221
<Button
201222
className={classNames(styles.shareButton)}
@@ -210,9 +231,11 @@ class MenuBar extends React.Component {
210231
);
211232
return (
212233
<Box
213-
className={classNames(styles.menuBar, {
214-
[styles.saveInProgress]: this.state.projectSaveInProgress
215-
})}
234+
className={classNames(
235+
this.props.className,
236+
styles.menuBar,
237+
{[styles.saveInProgress]: this.props.isUpdating}
238+
)}
216239
>
217240
<div className={styles.mainMenu}>
218241
<div className={styles.fileGroup}>
@@ -262,33 +285,35 @@ class MenuBar extends React.Component {
262285
place={this.props.isRtl ? 'left' : 'right'}
263286
onRequestClose={this.props.onRequestCloseFile}
264287
>
265-
<MenuItemTooltip
266-
id="new"
267-
isRtl={this.props.isRtl}
268-
>
269-
<MenuItem>
270-
<FormattedMessage
271-
defaultMessage="New"
272-
description="Menu bar item for creating a new project"
273-
id="gui.menuBar.new"
274-
/>
288+
{/* for now, only enable New when there is no session */}
289+
{this.props.sessionExists ? (
290+
<MenuItemTooltip
291+
id="new"
292+
isRtl={this.props.isRtl}
293+
>
294+
<MenuItem>{newProjectMessage}</MenuItem>
295+
</MenuItemTooltip>
296+
) : (
297+
<MenuItem
298+
isRtl={this.props.isRtl}
299+
onClick={this.handleClickNew}
300+
>
301+
{newProjectMessage}
275302
</MenuItem>
276-
</MenuItemTooltip>
303+
)}
277304
<MenuSection>
278-
<ProjectSaver>{(saveProject, updateProject) => (
279-
this.props.canUpdateProject ? (
280-
<MenuItem onClick={this.handleUpdateProject(updateProject)}>
281-
{saveNowMessage}
282-
</MenuItem>
283-
) : (
284-
<MenuItemTooltip
285-
id="save"
286-
isRtl={this.props.isRtl}
287-
>
288-
<MenuItem>{saveNowMessage}</MenuItem>
289-
</MenuItemTooltip>
290-
)
291-
)}</ProjectSaver>
305+
{this.props.canUpdateProject ? (
306+
<MenuItem onClick={this.handleClickSave}>
307+
{saveNowMessage}
308+
</MenuItem>
309+
) : (
310+
<MenuItemTooltip
311+
id="save"
312+
isRtl={this.props.isRtl}
313+
>
314+
<MenuItem>{saveNowMessage}</MenuItem>
315+
</MenuItemTooltip>
316+
)}
292317
<MenuItemTooltip
293318
id="copy"
294319
isRtl={this.props.isRtl}
@@ -303,30 +328,33 @@ class MenuBar extends React.Component {
303328
</MenuItemTooltip>
304329
</MenuSection>
305330
<MenuSection>
306-
<ProjectLoader>{(renderFileInput, loadProject, loadProps) => (
307-
<MenuItem
308-
onClick={loadProject}
309-
{...loadProps}
310-
>
311-
<FormattedMessage
312-
defaultMessage="Load from your computer"
313-
description="Menu bar item for uploading a project from your computer"
314-
id="gui.menuBar.uploadFromComputer"
315-
/>
316-
{renderFileInput()}
317-
</MenuItem>
318-
)}</ProjectLoader>
319-
<ProjectSaver>{saveProject => (
331+
<SBFileUploader>
332+
{(renderFileInput, loadProject) => (
333+
<MenuItem
334+
onClick={loadProject}
335+
>
336+
<FormattedMessage
337+
defaultMessage="Load from your computer"
338+
description={
339+
'Menu bar item for uploading a project from your computer'
340+
}
341+
id="gui.menuBar.uploadFromComputer"
342+
/>
343+
{renderFileInput()}
344+
</MenuItem>
345+
)}
346+
</SBFileUploader>
347+
<SB3Downloader>{downloadProject => (
320348
<MenuItem
321-
onClick={this.handleCloseFileMenuAndThen(saveProject)}
349+
onClick={this.handleCloseFileMenuAndThen(downloadProject)}
322350
>
323351
<FormattedMessage
324352
defaultMessage="Save to your computer"
325353
description="Menu bar item for downloading a project to your computer"
326354
id="gui.menuBar.downloadToComputer"
327355
/>
328356
</MenuItem>
329-
)}</ProjectSaver>
357+
)}</SB3Downloader>
330358
</MenuSection>
331359
</MenuBarMenu>
332360
</div>
@@ -588,18 +616,23 @@ class MenuBar extends React.Component {
588616
MenuBar.propTypes = {
589617
accountMenuOpen: PropTypes.bool,
590618
canUpdateProject: PropTypes.bool,
619+
className: PropTypes.string,
591620
editMenuOpen: PropTypes.bool,
592621
enableCommunity: PropTypes.bool,
593622
fileMenuOpen: PropTypes.bool,
594623
intl: intlShape,
595624
isRtl: PropTypes.bool,
625+
isShowingProject: PropTypes.bool,
626+
isUpdating: PropTypes.bool,
596627
languageMenuOpen: PropTypes.bool,
597628
loginMenuOpen: PropTypes.bool,
598629
onClickAccount: PropTypes.func,
599630
onClickEdit: PropTypes.func,
600631
onClickFile: PropTypes.func,
601632
onClickLanguage: PropTypes.func,
602633
onClickLogin: PropTypes.func,
634+
onClickNew: PropTypes.func,
635+
onClickSave: PropTypes.func,
603636
onLogOut: PropTypes.func,
604637
onOpenRegistration: PropTypes.func,
605638
onOpenTipLibrary: PropTypes.func,
@@ -609,25 +642,32 @@ MenuBar.propTypes = {
609642
onRequestCloseLanguage: PropTypes.func,
610643
onRequestCloseLogin: PropTypes.func,
611644
onSeeCommunity: PropTypes.func,
645+
onShare: PropTypes.func,
612646
onToggleLoginOpen: PropTypes.func,
613647
onUpdateProjectTitle: PropTypes.func,
614648
renderLogin: PropTypes.func,
615649
sessionExists: PropTypes.bool,
650+
startSaving: PropTypes.func,
616651
username: PropTypes.string
617652
};
618653

619-
const mapStateToProps = state => ({
620-
canUpdateProject: typeof (state.session && state.session.session && state.session.session.user) !== 'undefined',
621-
accountMenuOpen: accountMenuOpen(state),
622-
fileMenuOpen: fileMenuOpen(state),
623-
editMenuOpen: editMenuOpen(state),
624-
isRtl: state.locales.isRtl,
625-
languageMenuOpen: languageMenuOpen(state),
626-
loginMenuOpen: loginMenuOpen(state),
627-
sessionExists: state.session && typeof state.session.session !== 'undefined',
628-
username: state.session && state.session.session && state.session.session.user ?
629-
state.session.session.user.username : null
630-
});
654+
const mapStateToProps = state => {
655+
const loadingState = state.scratchGui.projectState.loadingState;
656+
const user = state.session && state.session.session && state.session.session.user;
657+
return {
658+
accountMenuOpen: accountMenuOpen(state),
659+
canUpdateProject: typeof user !== 'undefined',
660+
fileMenuOpen: fileMenuOpen(state),
661+
editMenuOpen: editMenuOpen(state),
662+
isRtl: state.locales.isRtl,
663+
isUpdating: getIsUpdating(loadingState),
664+
isShowingProject: getIsShowingProject(loadingState),
665+
languageMenuOpen: languageMenuOpen(state),
666+
loginMenuOpen: loginMenuOpen(state),
667+
sessionExists: state.session && typeof state.session.session !== 'undefined',
668+
username: user ? user.username : null
669+
};
670+
};
631671

632672
const mapDispatchToProps = dispatch => ({
633673
onOpenTipLibrary: () => dispatch(openTipsLibrary()),
@@ -641,7 +681,10 @@ const mapDispatchToProps = dispatch => ({
641681
onRequestCloseLanguage: () => dispatch(closeLanguageMenu()),
642682
onClickLogin: () => dispatch(openLoginMenu()),
643683
onRequestCloseLogin: () => dispatch(closeLoginMenu()),
644-
onSeeCommunity: () => dispatch(setPlayer(true))
684+
onClickNew: canSave => dispatch(requestNewProject(canSave)),
685+
onClickSave: () => dispatch(saveProject()),
686+
onSeeCommunity: () => dispatch(setPlayer(true)),
687+
onShare: () => {} // NOTE: implement this
645688
});
646689

647690
export default injectIntl(connect(

0 commit comments

Comments
 (0)