Newer
Older
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import analytics from '../lib/analytics';
import log from '../lib/log';
import {LoadingStates, onLoadedProject, onProjectUploadStarted} from '../reducers/project-state';
Karishma Chadha
committed
import {
openLoadingProject,
closeLoadingProject
} from '../reducers/modals';
* SBFileUploader component passes a file input, load handler and props to its child.
* It expects this child to be a function with the signature
* function (renderFileInput, loadProject) {}
* The component can then be used to attach project loading functionality
* to any other component:
*
* <SBFileUploader>{(renderFileInput, loadProject) => (
* <MyCoolComponent
* onClick={loadProject}
* >
* {renderFileInput()}
* </MyCoolComponent>
* )}</SBFileUploader>
const messages = defineMessages({
loadError: {
id: 'gui.projectLoader.loadError',
defaultMessage: 'The project file that was selected failed to load.',
description: 'An error that displays when a local project file fails to load.'
}
});
class SBFileUploader extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'setFileInput',
'handleChange',
'handleClick'
]);
}
Ben Wheeler
committed
getProjectTitleFromFilename (fileInputFilename) {
if (!fileInputFilename) return '';
// only parse title from files like "filename.sb2" or "filename.sb3"
const matches = fileInputFilename.match(/^(.*)\.sb[23]$/);
if (!matches) return '';
return matches[1].substring(0, 100); // truncate project title to max 100 chars
}
Ben Wheeler
committed
// called when user has finished selecting a file to upload
Karishma Chadha
committed
// Remove the hash if any (without triggering a hash change event or a reload)
history.replaceState({}, document.title, '.');
Karishma Chadha
committed
reader.onload = () => this.props.vm.loadProject(reader.result)
.then(() => {
analytics.event({
category: 'project',
action: 'Import Project File',
nonInteraction: true
});
this.props.onLoadingFinished(this.props.loadingState);
// Reset the file input after project is loaded
// This is necessary in case the user wants to reload a project
thisFileInput.value = null;
})
.catch(error => {
log.warn(error);
alert(this.props.intl.formatMessage(messages.loadError)); // eslint-disable-line no-alert
this.props.onLoadingFinished(this.props.loadingState);
// Reset the file input after project is loaded
// This is necessary in case the user wants to reload a project
thisFileInput.value = null;
Karishma Chadha
committed
});
if (thisFileInput.files) { // Don't attempt to load if no file was selected
this.props.onLoadingStarted();
reader.readAsArrayBuffer(thisFileInput.files[0]);
Ben Wheeler
committed
const uploadedProjectTitle = this.getProjectTitleFromFilename(thisFileInput.files[0].name);
this.props.onUpdateProjectTitle(uploadedProjectTitle);
Ben Wheeler
committed
// open filesystem browsing window
this.fileInput.click();
}
setFileInput (input) {
this.fileInput = input;
}
<input
accept=".sb2,.sb3"
ref={this.setFileInput}
style={{display: 'none'}}
type="file"
return this.props.children(this.renderFileInput, this.handleClick);
SBFileUploader.propTypes = {
intl: intlShape.isRequired,
loadingState: PropTypes.oneOf(LoadingStates),
onLoadingFinished: PropTypes.func,
onLoadingStarted: PropTypes.func,
onUpdateProjectTitle: PropTypes.func,
Karishma Chadha
committed
vm: PropTypes.shape({
loadProject: PropTypes.func
})
Karishma Chadha
committed
const mapStateToProps = state => ({
loadingState: state.scratchGui.projectState.loadingState,
Karishma Chadha
committed
});
const mapDispatchToProps = dispatch => ({
onLoadingFinished: loadingState => {
dispatch(onLoadedProject(loadingState));
dispatch(closeLoadingProject());
},
onLoadingStarted: () => {
dispatch(openLoadingProject());
dispatch(onProjectUploadStarted());
Ben Wheeler
committed
// Allow incoming props to override redux-provided props. Used to mock in tests.
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
{}, stateProps, dispatchProps, ownProps
);
Ben Wheeler
committed
mapDispatchToProps,
mergeProps
)(injectIntl(SBFileUploader));