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 (
+
+

+
+
+
+

+
+
+
+
+ );
+ })()}
{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,