mirror of
https://github.com/scratchfoundation/scratch-desktop.git
synced 2025-01-09 14:12:05 -05:00
set project title from save file name
This commit is contained in:
parent
5b028a1d3d
commit
00175f521d
5 changed files with 99 additions and 22 deletions
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -7640,6 +7640,12 @@
|
||||||
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==",
|
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.bindall": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.bindall/-/lodash.bindall-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-p7/Ugro9LnBxad/NyZPrv2w9eZg=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"lodash.defaultsdeep": {
|
"lodash.defaultsdeep": {
|
||||||
"version": "4.6.1",
|
"version": "4.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz",
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
"eslint-plugin-import": "^2.18.0",
|
"eslint-plugin-import": "^2.18.0",
|
||||||
"eslint-plugin-react": "^7.14.2",
|
"eslint-plugin-react": "^7.14.2",
|
||||||
"intl": "1.2.5",
|
"intl": "1.2.5",
|
||||||
|
"lodash.bindall": "^4.4.0",
|
||||||
"lodash.defaultsdeep": "^4.6.1",
|
"lodash.defaultsdeep": "^4.6.1",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"nets": "^3.2.0",
|
"nets": "^3.2.0",
|
||||||
|
|
|
@ -49,9 +49,25 @@ class ScratchDesktopTelemetry {
|
||||||
}
|
}
|
||||||
|
|
||||||
projectDidSave (metadata = {}) {
|
projectDidSave (metadata = {}) {
|
||||||
|
// Since the save dialog appears on the main process the GUI does not wait for the actual save to complete.
|
||||||
|
// That means the GUI sends this event before we know the file name used for the save, which is where the new
|
||||||
|
// project title comes from. Instead, just hold on to this metadata pending a `projectSaveCompleted` event
|
||||||
|
// from the save code on the main process. If the user cancels the save this data will be cleared.
|
||||||
|
this._pendingProjectSave = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
projectSaveCompleted (newProjectTitle) {
|
||||||
|
const metadata = this._pendingProjectSave;
|
||||||
|
this._pendingProjectSave = null;
|
||||||
|
|
||||||
|
metadata.projectName = newProjectTitle;
|
||||||
this._telemetryClient.addEvent('project::save', this._buildMetadata(metadata));
|
this._telemetryClient.addEvent('project::save', this._buildMetadata(metadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
projectSaveCanceled () {
|
||||||
|
this._pendingProjectSave = null;
|
||||||
|
}
|
||||||
|
|
||||||
projectWasCreated (metadata = {}) {
|
projectWasCreated (metadata = {}) {
|
||||||
this._telemetryClient.addEvent('project::create', this._buildMetadata(metadata));
|
this._telemetryClient.addEvent('project::create', this._buildMetadata(metadata));
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,13 +62,22 @@ const createMainWindow = () => {
|
||||||
if (extName) {
|
if (extName) {
|
||||||
const extNameNoDot = extName.replace(/^\./, '');
|
const extNameNoDot = extName.replace(/^\./, '');
|
||||||
const options = {
|
const options = {
|
||||||
|
defaultPath: baseName,
|
||||||
filters: [getFilterForExtension(extNameNoDot)]
|
filters: [getFilterForExtension(extNameNoDot)]
|
||||||
};
|
};
|
||||||
const userChosenPath = dialog.showSaveDialog(window, options);
|
const userChosenPath = dialog.showSaveDialog(window, options);
|
||||||
if (userChosenPath) {
|
if (userChosenPath) {
|
||||||
item.setSavePath(userChosenPath);
|
item.setSavePath(userChosenPath);
|
||||||
|
const newProjectTitle = path.basename(userChosenPath, extName);
|
||||||
|
webContents.send('setTitleFromSave', {title: newProjectTitle});
|
||||||
|
|
||||||
|
// "setTitleFromSave" will set the project title but GUI has already reported the telemetry event
|
||||||
|
// using the old title. This call lets the telemetry client know that the save was actually completed
|
||||||
|
// and the event should be committed to the event queue with this new title.
|
||||||
|
telemetry.projectSaveCompleted(newProjectTitle);
|
||||||
} else {
|
} else {
|
||||||
item.cancel();
|
item.cancel();
|
||||||
|
telemetry.projectSaveCanceled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import {ipcRenderer, shell} from 'electron';
|
import {ipcRenderer, shell} from 'electron';
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import GUI, {AppStateHOC} from 'scratch-gui';
|
import {compose} from 'redux';
|
||||||
|
import GUI, {AppStateHOC, TitledHOC} from 'scratch-gui';
|
||||||
|
|
||||||
import ElectronStorageHelper from '../common/ElectronStorageHelper';
|
import ElectronStorageHelper from '../common/ElectronStorageHelper';
|
||||||
|
|
||||||
|
@ -23,27 +26,69 @@ appTarget.className = styles.app || 'app'; // TODO
|
||||||
document.body.appendChild(appTarget);
|
document.body.appendChild(appTarget);
|
||||||
|
|
||||||
GUI.setAppElement(appTarget);
|
GUI.setAppElement(appTarget);
|
||||||
const WrappedGui = AppStateHOC(GUI);
|
|
||||||
|
|
||||||
const onStorageInit = storageInstance => {
|
const ScratchDesktopHOC = function (WrappedComponent) {
|
||||||
storageInstance.addHelper(new ElectronStorageHelper(storageInstance));
|
class ScratchDesktopComponent extends React.Component {
|
||||||
// storageInstance.addOfficialScratchWebStores(); // TODO: do we want this?
|
constructor (props) {
|
||||||
};
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
const guiProps = {
|
'handleProjectTelemetryEvent',
|
||||||
onStorageInit,
|
'handleSetTitleFromSave',
|
||||||
isScratchDesktop: true,
|
'handleStorageInit',
|
||||||
projectId: defaultProjectId,
|
'handleTelemetryModalOptIn',
|
||||||
showTelemetryModal: (typeof ipcRenderer.sendSync('getTelemetryDidOptIn')) !== 'boolean',
|
'handleTelemetryModalOptOut'
|
||||||
onTelemetryModalOptIn: () => {
|
]);
|
||||||
ipcRenderer.send('setTelemetryDidOptIn', true);
|
}
|
||||||
},
|
componentDidMount () {
|
||||||
onTelemetryModalOptOut: () => {
|
ipcRenderer.on('setTitleFromSave', this.handleSetTitleFromSave);
|
||||||
ipcRenderer.send('setTelemetryDidOptIn', false);
|
}
|
||||||
},
|
componentWillUnmount () {
|
||||||
onProjectTelemetryEvent: (event, metadata) => {
|
ipcRenderer.removeListener('setTitleFromSave', this.handleSetTitleFromSave);
|
||||||
ipcRenderer.send(event, metadata);
|
}
|
||||||
|
handleProjectTelemetryEvent (event, metadata) {
|
||||||
|
ipcRenderer.send(event, metadata);
|
||||||
|
}
|
||||||
|
handleSetTitleFromSave (event, args) {
|
||||||
|
this.props.onUpdateProjectTitle(args.title);
|
||||||
|
}
|
||||||
|
handleStorageInit (storageInstance) {
|
||||||
|
storageInstance.addHelper(new ElectronStorageHelper(storageInstance));
|
||||||
|
}
|
||||||
|
handleTelemetryModalOptIn () {
|
||||||
|
ipcRenderer.send('setTelemetryDidOptIn', true);
|
||||||
|
}
|
||||||
|
handleTelemetryModalOptOut () {
|
||||||
|
ipcRenderer.send('setTelemetryDidOptIn', false);
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
const shouldShowTelemetryModal = (typeof ipcRenderer.sendSync('getTelemetryDidOptIn') !== 'boolean');
|
||||||
|
return (<WrappedComponent
|
||||||
|
isScratchDesktop
|
||||||
|
projectId={defaultProjectId}
|
||||||
|
showTelemetryModal={shouldShowTelemetryModal}
|
||||||
|
onProjectTelemetryEvent={this.handleProjectTelemetryEvent}
|
||||||
|
onStorageInit={this.handleStorageInit}
|
||||||
|
onTelemetryModalOptIn={this.handleTelemetryModalOptIn}
|
||||||
|
onTelemetryModalOptOut={this.handleTelemetryModalOptOut}
|
||||||
|
{...this.props}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScratchDesktopComponent.propTypes = {
|
||||||
|
onUpdateProjectTitle: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
return ScratchDesktopComponent;
|
||||||
};
|
};
|
||||||
const wrappedGui = React.createElement(WrappedGui, guiProps);
|
|
||||||
ReactDOM.render(wrappedGui, appTarget);
|
// note that redux's 'compose' function is just being used as a general utility to make
|
||||||
|
// the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's
|
||||||
|
// ability to compose reducers.
|
||||||
|
const WrappedGui = compose(
|
||||||
|
AppStateHOC,
|
||||||
|
TitledHOC,
|
||||||
|
ScratchDesktopHOC // must come after `TitledHOC` so it has access to `onUpdateProjectTitle`
|
||||||
|
)(GUI);
|
||||||
|
|
||||||
|
ReactDOM.render(<WrappedGui />, appTarget);
|
||||||
|
|
Loading…
Reference in a new issue