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:
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}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
};
|
||||
Reference in new issue
Block a user