Move connection modal into redux (#3459)

* Connection Modal loads its own data

* Connection does not update flyout status buttons

* WIP moving connection modal into redux

* Lint fix and remove unused prop

* Close modal even if exception on disconnect
This commit is contained in:
Eric Rosenbaum authored and GitHub committed 2018-10-23 16:27:27 -04:00
1 parent db1d5bd25f
commit 31909d797f
8 files changed
+103 -59

No files matched your search

@@ -28,6 +28,7 @@ const ConnectionModalComponent = props => (
contentLabel={props.name}
headerClassName={styles.header}
headerImage={props.smallPeripheralImage}
id="connectionModal"
onHelp={props.onHelp}
onRequestClose={props.onCancel}
>
+7
View File
@@ -30,6 +30,7 @@ import TipsLibrary from '../../containers/tips-library.jsx';
import Cards from '../../containers/cards.jsx';
import Alerts from '../../containers/alerts.jsx';
import DragLayer from '../../containers/drag-layer.jsx';
import ConnectionModal from '../../containers/connection-modal.jsx';
import layout, {STAGE_SIZE_MODES} from '../../lib/layout-constants';
import {resolveStageSize} from '../../lib/screen-utils';
@@ -68,6 +69,7 @@ const GUIComponent = props => {
canSaveAsCopy,
canShare,
children,
connectionModalVisible,
costumeLibraryVisible,
costumesTabVisible,
enableCommunity,
@@ -157,6 +159,11 @@ const GUIComponent = props => {
{alertsVisible ? (
<Alerts className={styles.alertsContainer} />
) : null}
{connectionModalVisible ? (
<ConnectionModal
vm={vm}
/>
) : null}
{costumeLibraryVisible ? (
<CostumeLibrary
vm={vm}
+9 -30
View File
@@ -10,7 +10,6 @@ import VM from 'scratch-vm';
import analytics from '../lib/analytics';
import log from '../lib/log.js';
import Prompt from './prompt.jsx';
import ConnectionModal from './connection-modal.jsx';
import BlocksComponent from '../components/blocks/blocks.jsx';
import ExtensionLibrary from './extension-library.jsx';
import extensionData from '../lib/libraries/extensions/index.jsx';
@@ -21,8 +20,9 @@ import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants';
import {connect} from 'react-redux';
import {updateToolbox} from '../reducers/toolbox';
import {activateColorPicker} from '../reducers/color-picker';
import {closeExtensionLibrary, openSoundRecorder} from '../reducers/modals';
import {closeExtensionLibrary, openSoundRecorder, openConnectionModal} from '../reducers/modals';
import {activateCustomProcedures, deactivateCustomProcedures} from '../reducers/custom-procedures';
import {setConnectionModalExtensionId} from '../reducers/connection-modal';
import {
activateTab,
@@ -47,7 +47,6 @@ class Blocks extends React.Component {
'detachVM',
'handleCategorySelected',
'handleConnectionModalStart',
'handleConnectionModalClose',
'handleStatusButtonUpdate',
'handleOpenSoundRecorder',
'handlePromptStart',
@@ -73,8 +72,7 @@ class Blocks extends React.Component {
this.state = {
workspaceMetrics: {},
prompt: null,
connectionModal: null
prompt: null
};
this.onTargetsUpdate = debounce(this.onTargetsUpdate, 100);
this.toolboxUpdateQueue = [];
@@ -114,7 +112,6 @@ class Blocks extends React.Component {
shouldComponentUpdate (nextProps, nextState) {
return (
this.state.prompt !== nextState.prompt ||
this.state.connectionModal !== nextState.connectionModal ||
this.props.isVisible !== nextProps.isVisible ||
this.props.toolboxXML !== nextProps.toolboxXML ||
this.props.extensionLibraryVisible !== nextProps.extensionLibraryVisible ||
@@ -383,22 +380,7 @@ class Blocks extends React.Component {
this.setState(p);
}
handleConnectionModalStart (extensionId) {
const extension = extensionData.find(ext => ext.extensionId === extensionId);
if (extension) {
this.setState({connectionModal: {
extensionId: extensionId,
useAutoScan: extension.useAutoScan,
peripheralImage: extension.peripheralImage,
smallPeripheralImage: extension.smallPeripheralImage,
peripheralButtonImage: extension.peripheralButtonImage,
name: extension.name,
connectingMessage: extension.connectingMessage,
helpLink: extension.helpLink
}});
}
}
handleConnectionModalClose () {
this.setState({connectionModal: null});
this.props.onOpenConnectionModal(extensionId);
}
handleStatusButtonUpdate () {
this.ScratchBlocks.refreshStatusButtons(this.workspace);
@@ -434,6 +416,7 @@ class Blocks extends React.Component {
isRtl,
isVisible,
onActivateColorPicker,
onOpenConnectionModal,
onOpenSoundRecorder,
updateToolboxState,
onActivateCustomProcedures,
@@ -460,14 +443,6 @@ class Blocks extends React.Component {
onOk={this.handlePromptCallback}
/>
) : null}
{this.state.connectionModal ? (
<ConnectionModal
{...this.state.connectionModal}
vm={vm}
onCancel={this.handleConnectionModalClose}
onStatusButtonUpdate={this.handleStatusButtonUpdate}
/>
) : null}
{extensionLibraryVisible ? (
<ExtensionLibrary
vm={vm}
@@ -578,6 +553,10 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
onActivateColorPicker: callback => dispatch(activateColorPicker(callback)),
onActivateCustomProcedures: (data, callback) => dispatch(activateCustomProcedures(data, callback)),
onOpenConnectionModal: id => {
dispatch(setConnectionModalExtensionId(id));
dispatch(openConnectionModal());
},
onOpenSoundRecorder: () => {
dispatch(activateTab(SOUNDS_TAB_INDEX));
dispatch(openSoundRecorder());
+40 -26
View File
@@ -4,12 +4,16 @@ import bindAll from 'lodash.bindall';
import ConnectionModalComponent, {PHASES} from '../components/connection-modal/connection-modal.jsx';
import VM from 'scratch-vm';
import analytics from '../lib/analytics';
import extensionData from '../lib/libraries/extensions/index.jsx';
import {connect} from 'react-redux';
import {closeConnectionModal} from '../reducers/modals';
class ConnectionModal extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleScanning',
'handleCancel',
'handleConnected',
'handleConnecting',
'handleDisconnect',
@@ -17,6 +21,7 @@ class ConnectionModal extends React.Component {
'handleHelp'
]);
this.state = {
extension: extensionData.find(ext => ext.extensionId === props.extensionId),
phase: props.vm.getPeripheralIsConnected(props.extensionId) ?
PHASES.connected : PHASES.scanning
};
@@ -46,19 +51,24 @@ class ConnectionModal extends React.Component {
});
}
handleDisconnect () {
this.props.onStatusButtonUpdate(this.props.extensionId, 'not ready');
this.props.vm.disconnectPeripheral(this.props.extensionId);
this.props.onCancel();
try {
this.props.vm.disconnectPeripheral(this.props.extensionId);
} finally {
this.props.onCancel();
}
}
handleCancel () {
// If we're not connected to a peripheral, close the websocket so we stop scanning.
if (!this.props.vm.getPeripheralIsConnected(this.props.extensionId)) {
this.props.vm.disconnectPeripheral(this.props.extensionId);
try {
// If we're not connected to a peripheral, close the websocket so we stop scanning.
if (!this.props.vm.getPeripheralIsConnected(this.props.extensionId)) {
this.props.vm.disconnectPeripheral(this.props.extensionId);
}
} finally {
// Close the modal.
this.props.onCancel();
}
this.props.onCancel();
}
handleError () {
this.props.onStatusButtonUpdate();
// Assume errors that come in during scanning phase are the result of not
// having scratch-link installed.
if (this.state.phase === PHASES.scanning || this.state.phase === PHASES.unavailable) {
@@ -77,7 +87,6 @@ class ConnectionModal extends React.Component {
}
}
handleConnected () {
this.props.onStatusButtonUpdate();
this.setState({
phase: PHASES.connected
});
@@ -88,7 +97,7 @@ class ConnectionModal extends React.Component {
});
}
handleHelp () {
window.open(this.props.helpLink, '_blank');
window.open(this.state.extension.helpLink, '_blank');
analytics.event({
category: 'extensions',
action: 'help',
@@ -98,17 +107,17 @@ class ConnectionModal extends React.Component {
render () {
return (
<ConnectionModalComponent
connectingMessage={this.props.connectingMessage}
connectingMessage={this.state.extension.connectingMessage}
extensionId={this.props.extensionId}
name={this.props.name}
peripheralButtonImage={this.props.peripheralButtonImage}
peripheralImage={this.props.peripheralImage}
name={this.state.extension.name}
peripheralButtonImage={this.state.extension.peripheralButtonImage}
peripheralImage={this.state.extension.peripheralImage}
phase={this.state.phase}
smallPeripheralImage={this.props.smallPeripheralImage}
smallPeripheralImage={this.state.extension.smallPeripheralImage}
title={this.props.extensionId}
useAutoScan={this.props.useAutoScan}
useAutoScan={this.state.extension.useAutoScan}
vm={this.props.vm}
onCancel={this.props.onCancel}
onCancel={this.handleCancel}
onConnected={this.handleConnected}
onConnecting={this.handleConnecting}
onDisconnect={this.handleDisconnect}
@@ -120,17 +129,22 @@ class ConnectionModal extends React.Component {
}
ConnectionModal.propTypes = {
connectingMessage: PropTypes.node.isRequired,
extensionId: PropTypes.string.isRequired,
helpLink: PropTypes.string.isRequired,
name: PropTypes.node.isRequired,
onCancel: PropTypes.func.isRequired,
onStatusButtonUpdate: PropTypes.func.isRequired,
peripheralButtonImage: PropTypes.string,
peripheralImage: PropTypes.string.isRequired,
smallPeripheralImage: PropTypes.string.isRequired,
useAutoScan: PropTypes.bool.isRequired,
vm: PropTypes.instanceOf(VM).isRequired
};
export default ConnectionModal;
const mapStateToProps = state => ({
extensionId: state.scratchGui.connectionModal.extensionId
});
const mapDispatchToProps = dispatch => ({
onCancel: () => {
dispatch(closeConnectionModal());
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ConnectionModal);
+1
View File
@@ -132,6 +132,7 @@ const mapStateToProps = (state, ownProps) => {
backdropLibraryVisible: state.scratchGui.modals.backdropLibrary,
blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX,
cardsVisible: state.scratchGui.cards.visible,
connectionModalVisible: state.scratchGui.modals.connectionModal,
costumeLibraryVisible: state.scratchGui.modals.costumeLibrary,
costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
importInfoVisible: state.scratchGui.modals.importInfo,
+30
View File
@@ -0,0 +1,30 @@
const SET_ID = 'scratch-gui/connection-modal/setId';
const initialState = {
extensionId: null
};
const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
switch (action.type) {
case SET_ID:
return Object.assign({}, state, {
extensionId: action.extensionId
});
default:
return state;
}
};
const setConnectionModalExtensionId = function (extensionId) {
return {
type: SET_ID,
extensionId: extensionId
};
};
export {
reducer as default,
initialState as connectionModalInitialState,
setConnectionModalExtensionId
};
+3
View File
@@ -3,6 +3,7 @@ import alertsReducer, {alertsInitialState} from './alerts';
import assetDragReducer, {assetDragInitialState} from './asset-drag';
import cardsReducer, {cardsInitialState} from './cards';
import colorPickerReducer, {colorPickerInitialState} from './color-picker';
import connectionModalReducer, {connectionModalInitialState} from './connection-modal';
import customProceduresReducer, {customProceduresInitialState} from './custom-procedures';
import blockDragReducer, {blockDragInitialState} from './block-drag';
import editorTabReducer, {editorTabInitialState} from './editor-tab';
@@ -33,6 +34,7 @@ const guiInitialState = {
blockDrag: blockDragInitialState,
cards: cardsInitialState,
colorPicker: colorPickerInitialState,
connectionModal: connectionModalInitialState,
customProcedures: customProceduresInitialState,
editorTab: editorTabInitialState,
mode: modeInitialState,
@@ -100,6 +102,7 @@ const guiReducer = combineReducers({
blockDrag: blockDragReducer,
cards: cardsReducer,
colorPicker: colorPickerReducer,
connectionModal: connectionModalReducer,
customProcedures: customProceduresReducer,
editorTab: editorTabReducer,
mode: modeReducer,
+12 -3
View File
@@ -13,10 +13,9 @@ const MODAL_PREVIEW_INFO = 'previewInfo';
const MODAL_SOUND_LIBRARY = 'soundLibrary';
const MODAL_SPRITE_LIBRARY = 'spriteLibrary';
const MODAL_SOUND_RECORDER = 'soundRecorder';
const MODAL_CONNECTION = 'connectionModal';
const MODAL_TIPS_LIBRARY = 'tipsLibrary';
const initialState = {
[MODAL_BACKDROP_LIBRARY]: false,
[MODAL_CAMERA_CAPTURE]: false,
@@ -28,6 +27,7 @@ const initialState = {
[MODAL_SOUND_LIBRARY]: false,
[MODAL_SPRITE_LIBRARY]: false,
[MODAL_SOUND_RECORDER]: false,
[MODAL_CONNECTION]: false,
[MODAL_TIPS_LIBRARY]: false
};
@@ -98,6 +98,10 @@ const openSoundRecorder = function () {
analytics.pageview('/modals/microphone');
return openModal(MODAL_SOUND_RECORDER);
};
const openConnectionModal = function () {
analytics.pageview('/modals/connection');
return openModal(MODAL_CONNECTION);
};
const openTipsLibrary = function () {
analytics.pageview('/modals/tips');
return openModal(MODAL_TIPS_LIBRARY);
@@ -135,6 +139,9 @@ const closeSoundRecorder = function () {
const closeTipsLibrary = function () {
return closeModal(MODAL_TIPS_LIBRARY);
};
const closeConnectionModal = function () {
return closeModal(MODAL_CONNECTION);
};
export {
reducer as default,
initialState as modalsInitialState,
@@ -149,6 +156,7 @@ export {
openSpriteLibrary,
openSoundRecorder,
openTipsLibrary,
openConnectionModal,
closeBackdropLibrary,
closeCameraCapture,
closeCostumeLibrary,
@@ -159,5 +167,6 @@ export {
closeSpriteLibrary,
closeSoundLibrary,
closeSoundRecorder,
closeTipsLibrary
closeTipsLibrary,
closeConnectionModal
};