diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index a5a6d46db1d..eb2797d69f2 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -348,7 +348,7 @@ class MenuBar extends React.Component { } } handleConnectionMouseUp () { - if (this.props.deviceId) { + if (Object.keys(this.props.devices).length > 0) { this.props.onOpenConnectionModal(); } else { this.props.onDeviceIsEmpty(); @@ -384,8 +384,8 @@ class MenuBar extends React.Component { } } handleUploadFirmware () { - if (this.props.deviceId) { - this.props.vm.uploadFirmwareToPeripheral(this.props.deviceId); + if (Object.keys(this.props.devices).length > 0) { + this.props.vm.uploadFirmwareToPeripheral(this.props.devices.id); this.props.onSetRealtimeConnection(false); this.props.onOpenUploadProgress(); } else { @@ -670,6 +670,21 @@ class MenuBar extends React.Component { + {Object.values(this.props.devices).map(device => ( +
+ +
+ {device.name} +
+
+ ))}
- { - this.props.deviceName ? ( -
- {this.props.deviceName} -
- ) : ( - - )} +
{ stageSizeMode: state.scratchGui.stageSize.stageSize, vm: state.scratchGui.vm, peripheralName: state.scratchGui.connectionModal.peripheralName, - deviceId: state.scratchGui.device.deviceId, - deviceName: state.scratchGui.device.deviceName, - deviceNames: state.scratchGui.device.deviceName + devices: state.scratchGui.devices }; }; diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 634b892f162..f656db48960 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -30,6 +30,7 @@ import {activateCustomProcedures, deactivateCustomProcedures} from '../reducers/ import {updateMetrics} from '../reducers/workspace-metrics'; import {setCodeEditorValue} from '../reducers/code'; import {setDeviceId, setDeviceName, setDeviceType} from '../reducers/device'; +import {addDevice} from '../reducers/devices'; import {setSupportSwitchMode} from '../reducers/program-mode'; import {setBaudrate} from '../reducers/hardware-console'; @@ -412,9 +413,7 @@ class Blocks extends React.Component { const targetSounds = target.getSounds(); const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML(target); - const device = this.props.deviceData.find(item => item.deviceId === this.props.deviceId); - - return makeToolboxXML(false, device, target.isStage, target.id, dynamicBlocksXML, + return makeToolboxXML(false, this.props.devices, target.isStage, target.id, dynamicBlocksXML, this.props.isRealtimeMode, targetCostumes[targetCostumes.length - 1].name, stageCostumes[stageCostumes.length - 1].name, @@ -476,7 +475,7 @@ class Blocks extends React.Component { if (deviceId) { const dev = this.props.deviceData.find(ext => ext.deviceId === deviceId); - this.props.onDeviceSelected(dev.deviceId, dev.name, dev.type); + this.props.onDeviceSelected(dev); this.ScratchBlocks.Device.setDevice(dev.deviceId, dev.type); if (dev.defaultBaudRate) { this.props.onSetBaudrate(dev.defaultBaudRate); @@ -560,7 +559,7 @@ class Blocks extends React.Component { } handleScratchExtensionRemoved (extensionInfo) { if (extensionInfo && extensionInfo.deviceId) { - this.props.onDeviceSelected(null, null, null); + this.props.onDeviceSelected(null); this.props.vm.runtime.setRealtimeMode(true); this.props.onSetSupportSwitchMode(false); } @@ -570,6 +569,7 @@ class Blocks extends React.Component { } } handleDeviceExtensionAdded (deviceExtensionsRegister) { + console.log('[handleDeviceExtensionAdded]'); if (deviceExtensionsRegister.defineMessages) { this.ScratchBlocks = deviceExtensionsRegister.defineMessages(this.ScratchBlocks); } @@ -588,6 +588,7 @@ class Blocks extends React.Component { } } handleDeviceExtensionRemoved () { + console.log('[handleDeviceExtensionRemoved]'); const toolboxXML = this.getToolboxXML(); if (toolboxXML) { this.props.updateToolboxState(toolboxXML); @@ -607,15 +608,13 @@ class Blocks extends React.Component { this.workspace.toolbox_.setSelectedCategoryById(categoryId); }); } - handleDeviceSelected (categoryId) { - const device = this.props.deviceData.find(ext => ext.deviceId === categoryId); - + handleDeviceSelected (device) { + console.log('[handleDeviceSelected] device:', device); if (device && device.launchPeripheralConnectionFlow) { this.handleConnectionModalStart(); } - this.withToolboxUpdates(() => { - this.workspace.toolbox_.setSelectedCategoryById(categoryId); + this.workspace.toolbox_.setSelectedCategoryById(device.deviceId); }); } setBlocks (blocks) { @@ -703,9 +702,8 @@ class Blocks extends React.Component { canUseCloud, customProceduresVisible, deviceData, - deviceId, + devices, deviceLibraryVisible, - deviceType, peripheralName, extensionLibraryVisible, options, @@ -789,8 +787,7 @@ Blocks.propTypes = { canUseCloud: PropTypes.bool, customProceduresVisible: PropTypes.bool, deviceData: PropTypes.instanceOf(Array).isRequired, - deviceId: PropTypes.string, - deviceType: PropTypes.string, + devices: PropTypes.objectOf(PropTypes.object), peripheralName: PropTypes.string, deviceLibraryVisible: PropTypes.bool, extensionLibraryVisible: PropTypes.bool, @@ -891,8 +888,7 @@ const mapStateToProps = state => ({ state.scratchGui.mode.isFullScreen ), deviceData: state.scratchGui.deviceData.deviceData, - deviceId: state.scratchGui.device.deviceId, - deviceType: state.scratchGui.device.deviceType, + devices: state.scratchGui.devices, peripheralName: state.scratchGui.connectionModal.peripheralName, deviceLibraryVisible: state.scratchGui.modals.deviceLibrary, extensionLibraryVisible: state.scratchGui.modals.extensionLibrary, @@ -909,10 +905,11 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ onActivateColorPicker: callback => dispatch(activateColorPicker(callback)), onActivateCustomProcedures: (data, callback) => dispatch(activateCustomProcedures(data, callback)), - onDeviceSelected: (id, name, type) => { - dispatch(setDeviceId(id)); - dispatch(setDeviceName(name)); - dispatch(setDeviceType(type)); + onDeviceSelected: device => { + dispatch(addDevice(device)); + dispatch(setDeviceId(device.deviceId)); + dispatch(setDeviceName(device.name)); + dispatch(setDeviceType(device.type)); }, onOpenConnectionModal: () => { dispatch(openConnectionModal()); diff --git a/src/containers/device-library.jsx b/src/containers/device-library.jsx index b353b29af7f..859f9494f8f 100644 --- a/src/containers/device-library.jsx +++ b/src/containers/device-library.jsx @@ -66,20 +66,22 @@ class DeviceLibrary extends React.PureComponent { requestLoadDevice (device) { const id = device.deviceId; + const deviceType = device.type; + const pnpidList = device.pnpidList; const deviceExtensions = device.deviceExtensions; if (id && !device.disabled) { if (this.props.vm.extensionManager.isDeviceLoaded(id)) { - this.props.onDeviceSelected(id); + this.props.onDeviceSelected(device); } else { - this.props.vm.extensionManager.loadDeviceURL(device).then(() => { + this.props.vm.extensionManager.loadDeviceURL(id, deviceType, pnpidList).then(() => { this.props.vm.extensionManager.getDeviceExtensionsList().then(() => { // TODO: Add a event for install device extension // the large extensions will take many times to load // A loading interface should be launched. this.props.vm.installDeviceExtensions(Object.assign([], deviceExtensions)); }); - this.props.onDeviceSelected(id); + this.props.onDeviceSelected(device); analytics.event({ category: 'devices', action: 'select device', diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 867373de74a..c3aeaf65807 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -723,7 +723,7 @@ const xmlClose = ''; /** * @param {!boolean} isInitialSetup - Whether the toolbox is for initial setup. If the mode is "initial setup", * blocks with localized default parameters (e.g. ask and wait) should not be loaded. (LLK/scratch-gui#5445) - * @param {?object} device - Full data of current selected deivce. + * @param {?object} devices - Full data of current selected deivces. * @param {?boolean} isStage - Whether the toolbox is for a stage-type target. This is always set to true * when isInitialSetup is true. * @param {?string} targetId - The current editing target @@ -737,7 +737,7 @@ const xmlClose = ''; * @param {?string} soundName - The name of the default selected sound dropdown. * @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox. */ -const makeToolboxXML = function (isInitialSetup, device = null, isStage = true, targetId, categoriesXML = [], +const makeToolboxXML = function (isInitialSetup, devices = null, isStage = true, targetId, categoriesXML = [], isRealtimeMode = true, costumeName = '', backdropName = '', soundName = '') { isStage = isInitialSetup || isStage; @@ -759,10 +759,11 @@ const makeToolboxXML = function (isInitialSetup, device = null, isStage = true, let everything = [xmlOpen]; - if (device) { + console.log('[makeToolboxXML] devices:', devices); + if (devices && Object.keys(devices).length > 0) { + const device = devices[Object.keys(devices)[0]]; const baseToolboxXml = device.baseToolBoxXml(isInitialSetup, isStage, targetId, isRealtimeMode, costumeName, backdropName, soundName); - everything = everything.concat(baseToolboxXml); } else { const motionXML = moveCategory('motion') || motion(isInitialSetup, isStage, targetId); diff --git a/src/reducers/devices.js b/src/reducers/devices.js new file mode 100644 index 00000000000..e1aba567a70 --- /dev/null +++ b/src/reducers/devices.js @@ -0,0 +1,44 @@ +const ADD_DEVICE = 'scratch-gui/devices/add'; +const REMOVE_DEVICE = 'scratch-gui/devices/remove'; + +const initialState = { +}; + +const reducer = function (state, action) { + if (typeof state === 'undefined') state = initialState; + const newState = Object.assign({}, state); + switch (action.type) { + case ADD_DEVICE: { + newState[action.deviceId] = action.device; + return newState; + } + case REMOVE_DEVICE: { + delete newState[action.deviceId]; + return newState; + } + default: + return state; + } +}; + +const addDevice = function (device) { + return { + type: ADD_DEVICE, + deviceId: device.deviceId, + device: device + }; +}; + +const removeDevice = function (deviceId) { + return { + type: REMOVE_DEVICE, + deviceId: deviceId + }; +}; + +export { + reducer as default, + initialState as devicesInitialState, + addDevice, + removeDevice +}; diff --git a/src/reducers/gui.js b/src/reducers/gui.js index 37e94186451..8bc083a9e70 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -31,6 +31,7 @@ import programModeReducer, {programModeInitialState} from './program-mode'; import codeReducer, {codeInitialState} from './code'; import deviceReducer, {deviceInitialState} from './device'; import deviceDataReducer, {deviceDataInitialState} from './device-data'; +import devicesReducer, {devicesInitialState} from './devices'; import hardwareConsoleReducer, {hardwareConsoleInitialState} from './hardware-console'; import updateReducer, {updateInitialState} from './update'; @@ -49,6 +50,7 @@ const guiInitialState = { customProcedures: customProceduresInitialState, device: deviceInitialState, deviceData: deviceDataInitialState, + devices: devicesInitialState, hardwareConsole: hardwareConsoleInitialState, editorTab: editorTabInitialState, mode: modeInitialState, @@ -154,6 +156,7 @@ const guiReducer = combineReducers({ customProcedures: customProceduresReducer, device: deviceReducer, deviceData: deviceDataReducer, + devices: devicesReducer, editorTab: editorTabReducer, mode: modeReducer, hardwareConsole: hardwareConsoleReducer,