diff --git a/package-lock.json b/package-lock.json index 3adbd53f176..bdd42dcdd4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28942,7 +28942,7 @@ }, "node_modules/scratch-vm": { "version": "5.0.300", - "resolved": "git+ssh://git@github.com/smalruby/scratch-vm.git#2ddca55f7d10e8549251181a09a5d0506bc474e6", + "resolved": "git+ssh://git@github.com/smalruby/scratch-vm.git#db5982d2756da0fa7128e0b17bf6bbe54d2f2a4a", "license": "AGPL-3.0-only", "dependencies": { "@vernier/godirect": "^1.5.0", diff --git a/src/components/menu-bar/icon--mesh-connected.png b/src/components/menu-bar/icon--mesh-connected.png new file mode 100644 index 00000000000..3528e4150c9 Binary files /dev/null and b/src/components/menu-bar/icon--mesh-connected.png differ diff --git a/src/components/menu-bar/icon--mesh-disconnected.png b/src/components/menu-bar/icon--mesh-disconnected.png new file mode 100644 index 00000000000..b5b794660d2 Binary files /dev/null and b/src/components/menu-bar/icon--mesh-disconnected.png differ diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index a7680a00a3f..6472750b4d5 100644 --- a/src/components/menu-bar/menu-bar.css +++ b/src/components/menu-bar/menu-bar.css @@ -219,6 +219,12 @@ vertical-align: middle; } +.mesh-icon { + width: 21px; + height: 21px; + margin-right: 0.5rem; +} + .collapsible-label { margin: 0 .5rem 0 .25rem; } diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index d9e5d08bc05..de98adc5f61 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -34,7 +34,8 @@ import GoogleDriveSaverHOC from '../../containers/google-drive-saver-hoc.jsx'; import GoogleDriveSaveDialog from '../google-drive-save-dialog/google-drive-save-dialog.jsx'; import SettingsMenu from './settings-menu.jsx'; -import {openDebugModal, openKoshienTestModal} from '../../reducers/modals'; +import {openDebugModal, openKoshienTestModal, openConnectionModal} from '../../reducers/modals'; +import {setConnectionModalExtensionId} from '../../reducers/connection-modal'; import {openBlockDisplayModal} from '../../reducers/block-display'; import {setPlayer} from '../../reducers/mode'; import { @@ -76,6 +77,9 @@ import { openKoshienMenu, closeKoshienMenu, koshienMenuOpen, + openMeshV2Menu, + closeMeshV2Menu, + meshV2MenuOpen, openLoginMenu, closeLoginMenu, loginMenuOpen, @@ -101,6 +105,8 @@ import fileIcon from './icon--file.svg'; import editIcon from './icon--edit.svg'; import debugIcon from '../debug-modal/icons/icon--debug.svg'; import koshienIcon from './icon--koshien.svg'; +import meshConnectedIcon from './icon--mesh-connected.png'; +import meshDisconnectedIcon from './icon--mesh-disconnected.png'; import smalrubyLogo from './hatti.svg'; @@ -219,6 +225,7 @@ class MenuBar extends React.Component { 'handleSaveDirectlyToGoogleDrive', 'handleExtensionAdded', 'handleClickKoshienEntryForm', + 'handleMeshV2MenuClick', 'handleClickLearn' ]); } @@ -228,6 +235,9 @@ class MenuBar extends React.Component { // Listen for extension load events if (this.props.vm.runtime) { this.props.vm.runtime.on('EXTENSION_ADDED', this.handleExtensionAdded); + this.props.vm.runtime.on('PERIPHERAL_CONNECTED', this.handleExtensionAdded); + this.props.vm.runtime.on('PERIPHERAL_DISCONNECTED', this.handleExtensionAdded); + this.props.vm.runtime.on('PERIPHERAL_REQUEST_ERROR', this.handleExtensionAdded); } } componentWillUnmount () { @@ -236,6 +246,9 @@ class MenuBar extends React.Component { // Remove extension listener if (this.props.vm.runtime) { this.props.vm.runtime.off('EXTENSION_ADDED', this.handleExtensionAdded); + this.props.vm.runtime.off('PERIPHERAL_CONNECTED', this.handleExtensionAdded); + this.props.vm.runtime.off('PERIPHERAL_DISCONNECTED', this.handleExtensionAdded); + this.props.vm.runtime.off('PERIPHERAL_REQUEST_ERROR', this.handleExtensionAdded); } } handleExtensionAdded () { @@ -244,6 +257,52 @@ class MenuBar extends React.Component { this.props.onExtensionLoaded(); } } + getMeshV2Status () { + const vm = this.props.vm; + + if (!vm) return {loaded: false}; + + // In Smalruby 3 / Scratch 3, extensionManager is directly on the vm instance + const extensionManager = vm.extensionManager; + if (!extensionManager) { + return {loaded: false}; + } + + const isLoaded = extensionManager.isExtensionLoaded('meshV2'); + + if (!isLoaded) { + return {loaded: false}; + } + + // peripheralExtensions is on vm.runtime + const runtime = vm.runtime; + if (!runtime || !runtime.peripheralExtensions) { + return {loaded: true, connected: false}; + } + + const extension = runtime.peripheralExtensions.meshV2; + + if (!extension) { + return {loaded: true, connected: false}; + } + + const connected = extension.connectionState === 'connected'; + const message = extension.menuMessage(); + + return { + loaded: true, + connected: connected, + message: message, + icon: connected ? meshConnectedIcon : meshDisconnectedIcon + }; + } + handleMeshV2MenuClick () { + // Close the Mesh V2 menu + this.props.onRequestCloseMeshV2(); + + // Open connection modal + this.props.onOpenConnectionModal('meshV2'); + } handleClickNew () { // if the project is dirty, and user owns the project, we will autosave. // but if they are not logged in and can't save, user should consider @@ -871,6 +930,42 @@ class MenuBar extends React.Component { + {(() => { + const meshV2Status = this.getMeshV2Status(); + if (!meshV2Status.loaded) return null; + + return ( +
+ + + + + + + + {meshV2Status.message} + + +
+ ); + })()} {this.props.vm.extensionManager && this.props.vm.extensionManager.isExtensionLoaded('koshien') && (
{ fileMenuOpen: fileMenuOpen(state), editMenuOpen: editMenuOpen(state), koshienMenuOpen: koshienMenuOpen(state), + meshV2MenuOpen: meshV2MenuOpen(state), extensionLoadCounter: state.scratchGui.koshienFile.extensionLoadCounter, aiSaveStatus: state.scratchGui.koshienFile.aiSaveStatus, googleDriveFile: state.scratchGui.googleDriveFile, @@ -1361,6 +1461,10 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = dispatch => ({ autoUpdateProject: () => dispatch(autoUpdateProject()), onOpenDebugModal: () => dispatch(openDebugModal()), + onOpenConnectionModal: id => { + dispatch(setConnectionModalExtensionId(id)); + dispatch(openConnectionModal()); + }, onOpenBlockDisplayModal: () => dispatch(openBlockDisplayModal()), onOpenKoshienTestModal: () => dispatch(openKoshienTestModal()), onClickAccount: () => dispatch(openAccountMenu()), @@ -1371,6 +1475,8 @@ const mapDispatchToProps = dispatch => ({ onRequestCloseEdit: () => dispatch(closeEditMenu()), onClickKoshien: () => dispatch(openKoshienMenu()), onRequestCloseKoshien: () => dispatch(closeKoshienMenu()), + onClickMeshV2: () => dispatch(openMeshV2Menu()), + onRequestCloseMeshV2: () => dispatch(closeMeshV2Menu()), onClickLogin: () => dispatch(openLoginMenu()), onRequestCloseLogin: () => dispatch(closeLoginMenu()), onClickMode: () => dispatch(openModeMenu()), diff --git a/src/locales/en.js b/src/locales/en.js index d0f323e3fc0..9c1c2376cfb 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -7,6 +7,7 @@ export default { "gui.smalruby3.previewInfo.invitation": "We're working on the next generation of Smalruby. We're excited for you to try it!", "gui.smalruby3.previewInfo.welcome": "Welcome to the Smalruby 3.0 Preview", 'gui.smalruby3.menuBar.downloadRubyCodeToComputer': 'Save Ruby to your computer', + "gui.menuBar.meshV2": "Mesh", "gui.smalruby3.menuBar.generateRubyFromCode": "Generate Ruby from Code", "gui.smalruby3.unsupportedBrowser.notRecommended": "We are very sorry, but it looks like you are using a browser version that Smalruby 3.0 does not support. We recommend updating to the latest version of a supported browser such as Google Chrome, Mozilla Firefox, Microsoft Edge, or Apple Safari. ", "gui.smalruby3.unsupportedBrowser.description": "We're very sorry, but Smalruby 3.0 does not support Internet Explorer, Vivaldi, Opera or Silk. We recommend trying a newer browser such as Google Chrome, Mozilla Firefox, or Microsoft Edge.", @@ -28,6 +29,9 @@ export default { 'mesh.clientPeripheralName': 'Join Mesh [{ MESH_ID }]', 'mesh.registeredHost': 'Registered Host Mesh [{ MESH_ID }]', 'mesh.joinedMesh': 'Joined Mesh [{ MESH_ID }]', + 'mesh.notConnectedMenu': '! Disconnected', + 'mesh.registeredHostMenu': '✔【{ MESH_ID }】 ⏳️{ EXPIRES_AT }', + 'mesh.joinedMeshMenu': '✔【{ MESH_ID }】 ⏳️{ EXPIRES_AT }', 'gui.smalruby3.extension.mesh.connectingMessage': 'Connecting', 'gui.smalruby3.extension.meshV2.name': 'Mesh V2', 'mesh.notConnected': 'Not connected (Mesh)', diff --git a/src/locales/ja-Hira.js b/src/locales/ja-Hira.js index 09f1c8572f0..5d34dc38247 100644 --- a/src/locales/ja-Hira.js +++ b/src/locales/ja-Hira.js @@ -1,5 +1,6 @@ export default { 'gui.modal.reload': 'さいよみこみ', + 'gui.menuBar.meshV2': 'メッシュ', 'gui.menuBar.loadFromUrl': 'Scratchからよみこむ', 'gui.urlLoader.loadError': 'プロジェクトURLのよみこみにしっぱいしました。', 'gui.urlLoader.invalidUrl': 'ゆうこうなScratchプロジェクトURLをにゅうりょくしてください。', @@ -43,6 +44,9 @@ export default { 'mesh.registeredHost': 'ホストとしてメッシュにとうろくしました 【{ MESH_ID }】', 'mesh.notConnected': 'メッシュにせつぞくしていません', 'mesh.joinedMesh': 'メッシュにさんかしました 【{ MESH_ID }】', + 'mesh.notConnectedMenu': '!せつぞくしていません', + 'mesh.registeredHostMenu': '✔【{ MESH_ID }】 ⏳️{ EXPIRES_AT }まで', + 'mesh.joinedMeshMenu': '✔【{ MESH_ID }】 ⏳️{ EXPIRES_AT }まで', 'gui.smalruby3.extension.smalrubotS1.name': 'スモウルボットS1 (エス1)', 'gui.smalruby3.extension.smalrubotS1.description': 'スモウルボットS1 (エス1) をせいぎょする。', 'gui.smalruby3.extension.smalrubotS1.connectingMessage': 'スモウルボットS1 (エス1) にせつぞくしています。', diff --git a/src/locales/ja.js b/src/locales/ja.js index 71baa8f79b2..175505f1189 100644 --- a/src/locales/ja.js +++ b/src/locales/ja.js @@ -2,6 +2,7 @@ export default { 'gui.modal.reload': '再読み込み', 'gui.modal.stop': '中止', 'gui.menuBar.loadFromUrl': 'Scratchから読み込む', + 'gui.menuBar.meshV2': 'メッシュ', 'gui.menuBar.loadFromGoogleDrive': 'Google ドライブから読み込む', 'gui.menuBar.saveToGoogleDrive': 'Googleドライブにコピーを保存...', 'gui.menuBar.saveDirectlyToGoogleDrive': 'Googleドライブに直ちに保存', @@ -75,11 +76,14 @@ export default { 'mesh.sensorValue': '[NAME] センサーの値', 'mesh.hostPeripheralName': 'メッシュのホストになる 【{ MESH_ID }】', 'mesh.clientPeripheralName': 'メッシュに参加する 【{ MESH_ID }】', - 'mesh.registeredHost': 'ホストとしてメッシュに登録しました 【{ MESH_ID }】', - 'mesh.joinedMesh': 'メッシュに参加しました 【{ MESH_ID }】', 'gui.smalruby3.extension.mesh.connectingMessage': 'メッシュのネットワークに接続しています', 'gui.smalruby3.extension.meshV2.name': 'メッシュ V2', 'mesh.notConnected': 'メッシュに接続していません', + 'mesh.registeredHost': 'ホストとしてメッシュに登録しました 【{ MESH_ID }】', + 'mesh.joinedMesh': 'メッシュに参加しました 【{ MESH_ID }】', + 'mesh.notConnectedMenu': '!未接続', + 'mesh.registeredHostMenu': '✔【{ MESH_ID }】 ⏳️{ EXPIRES_AT }まで', + 'mesh.joinedMeshMenu': '✔【{ MESH_ID }】 ⏳️{ EXPIRES_AT }まで', 'gui.smalruby3.extension.smalrubotS1.name': 'スモウルボットS1', 'gui.smalruby3.extension.smalrubotS1.description': 'スモウルボットS1を制御する。', 'gui.smalruby3.extension.smalrubotS1.connectingMessage': 'スモウルボットS1に接続しています。', diff --git a/src/reducers/menus.js b/src/reducers/menus.js index 00765a0b868..4c5d5fae2bc 100644 --- a/src/reducers/menus.js +++ b/src/reducers/menus.js @@ -6,6 +6,7 @@ const MENU_ACCOUNT = 'accountMenu'; const MENU_EDIT = 'editMenu'; const MENU_FILE = 'fileMenu'; const MENU_KOSHIEN = 'koshienMenu'; +const MENU_MESH_V2 = 'meshV2Menu'; const MENU_LANGUAGE = 'languageMenu'; const MENU_LOGIN = 'loginMenu'; const MENU_MODE = 'modeMenu'; @@ -58,6 +59,7 @@ const rootMenu = new Menu('root') .addChild(new Menu(MENU_EDIT)) .addChild(new Menu(MENU_MODE)) .addChild(new Menu(MENU_KOSHIEN)) + .addChild(new Menu(MENU_MESH_V2)) .addChild(new Menu(MENU_SETTINGS)) .addChild(new Menu(MENU_LOGIN)) .addChild(new Menu(MENU_ACCOUNT)) @@ -69,6 +71,7 @@ const initialState = { [MENU_EDIT]: false, [MENU_FILE]: false, [MENU_KOSHIEN]: false, + [MENU_MESH_V2]: false, [MENU_LANGUAGE]: false, [MENU_LOGIN]: false, [MENU_MODE]: false, @@ -153,6 +156,10 @@ const openKoshienMenu = () => openMenu(MENU_KOSHIEN); const closeKoshienMenu = () => closeMenu(MENU_KOSHIEN); const koshienMenuOpen = state => state.scratchGui.menus[MENU_KOSHIEN]; +const openMeshV2Menu = () => openMenu(MENU_MESH_V2); +const closeMeshV2Menu = () => closeMenu(MENU_MESH_V2); +const meshV2MenuOpen = state => state.scratchGui.menus[MENU_MESH_V2]; + export { reducer as default, initialState as menuInitialState, @@ -171,6 +178,9 @@ export { openKoshienMenu, closeKoshienMenu, koshienMenuOpen, + openMeshV2Menu, + closeMeshV2Menu, + meshV2MenuOpen, openLanguageMenu, closeLanguageMenu, languageMenuOpen,