Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Commit 24f80c9

Browse files
committed
feat: make meshV2 domain configurable from GUI
- Add MeshDomainModal component to set MeshV2 domain - Add Redux state to manage domain information - Update MenuBar to show current domain and provide setting option - Add connection check before allowing domain changes - Add translations (Japanese/English)
1 parent cf9f2c2 commit 24f80c9

11 files changed

Lines changed: 383 additions & 17 deletions

File tree

src/components/gui/gui.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import DragLayer from '../../containers/drag-layer.jsx';
3030
import ConnectionModal from '../../containers/connection-modal.jsx';
3131
import TelemetryModal from '../telemetry-modal/telemetry-modal.jsx';
3232
import BlockDisplayModal from '../../containers/block-display-modal.jsx';
33+
import MeshDomainModal from '../../containers/mesh-domain-modal.jsx';
3334
import URLLoaderModal from '../url-loader-modal/url-loader-modal.jsx';
3435
import KoshienTestModal from '../koshien-test-modal/koshien-test-modal.jsx';
3536

@@ -98,6 +99,7 @@ const GUIComponent = props => {
9899
isTotallyNormal,
99100
loading,
100101
logo,
102+
meshDomainModalVisible,
101103
renderLogin,
102104
onClickAbout,
103105
onClickAccountNav,
@@ -198,6 +200,9 @@ const GUIComponent = props => {
198200
onLoadUrl={onUrlLoaderSubmit}
199201
/>
200202
) : null}
203+
{meshDomainModalVisible ? (
204+
<MeshDomainModal />
205+
) : null}
201206
{koshienTestModalVisible ? (
202207
<KoshienTestModal
203208
onRequestClose={closeKoshienTestModal}
@@ -458,6 +463,7 @@ GUIComponent.propTypes = {
458463
isTotallyNormal: PropTypes.bool,
459464
loading: PropTypes.bool,
460465
logo: PropTypes.string,
466+
meshDomainModalVisible: PropTypes.bool,
461467
onActivateCostumesTab: PropTypes.func,
462468
onActivateRubyTab: PropTypes.func,
463469
onActivateSoundsTab: PropTypes.func,

src/components/menu-bar/menu-bar.jsx

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,15 @@ import GoogleDriveSaverHOC from '../../containers/google-drive-saver-hoc.jsx';
3434
import GoogleDriveSaveDialog from '../google-drive-save-dialog/google-drive-save-dialog.jsx';
3535
import SettingsMenu from './settings-menu.jsx';
3636

37-
import {openDebugModal, openKoshienTestModal, openConnectionModal} from '../../reducers/modals';
37+
import {
38+
openDebugModal,
39+
openKoshienTestModal,
40+
openMeshDomainModal,
41+
openConnectionModal
42+
} from '../../reducers/modals';
43+
import {
44+
setDomain as setMeshV2Domain
45+
} from '../../reducers/mesh-v2';
3846
import {setConnectionModalExtensionId} from '../../reducers/connection-modal';
3947
import {openBlockDisplayModal} from '../../reducers/block-display';
4048
import {setPlayer} from '../../reducers/mode';
@@ -226,29 +234,26 @@ class MenuBar extends React.Component {
226234
'handleExtensionAdded',
227235
'handleClickKoshienEntryForm',
228236
'handleMeshV2MenuClick',
237+
'handleMeshDomainClick',
229238
'handleClickLearn'
230239
]);
231240
}
232241
componentDidMount () {
233242
document.addEventListener('keydown', this.handleKeyPress);
234-
235-
// Listen for extension load events
236-
if (this.props.vm.runtime) {
237-
this.props.vm.runtime.on('EXTENSION_ADDED', this.handleExtensionAdded);
238-
this.props.vm.runtime.on('PERIPHERAL_CONNECTED', this.handleExtensionAdded);
239-
this.props.vm.runtime.on('PERIPHERAL_DISCONNECTED', this.handleExtensionAdded);
240-
this.props.vm.runtime.on('PERIPHERAL_REQUEST_ERROR', this.handleExtensionAdded);
243+
this.syncMeshV2Domain();
244+
}
245+
componentDidUpdate (prevProps) {
246+
if (this.props.extensionLoadCounter !== prevProps.extensionLoadCounter) {
247+
this.syncMeshV2Domain();
241248
}
242249
}
243250
componentWillUnmount () {
244251
document.removeEventListener('keydown', this.handleKeyPress);
245-
246-
// Remove extension listener
247-
if (this.props.vm.runtime) {
248-
this.props.vm.runtime.off('EXTENSION_ADDED', this.handleExtensionAdded);
249-
this.props.vm.runtime.off('PERIPHERAL_CONNECTED', this.handleExtensionAdded);
250-
this.props.vm.runtime.off('PERIPHERAL_DISCONNECTED', this.handleExtensionAdded);
251-
this.props.vm.runtime.off('PERIPHERAL_REQUEST_ERROR', this.handleExtensionAdded);
252+
}
253+
syncMeshV2Domain () {
254+
const extension = this.props.vm.runtime.peripheralExtensions.meshV2;
255+
if (extension && extension.domain !== this.props.meshV2Domain) {
256+
this.props.onSetMeshV2Domain(extension.domain);
252257
}
253258
}
254259
handleExtensionAdded () {
@@ -303,6 +308,20 @@ class MenuBar extends React.Component {
303308
// Open connection modal
304309
this.props.onOpenConnectionModal('meshV2');
305310
}
311+
handleMeshDomainClick () {
312+
// Close the Mesh V2 menu
313+
this.props.onRequestCloseMeshV2();
314+
315+
const extension = this.props.vm.runtime.peripheralExtensions.meshV2;
316+
if (extension && (extension.connectionState === 'connected' || extension.connectionState === 'connecting')) {
317+
alert(this.props.intl.formatMessage({ // eslint-disable-line no-alert
318+
id: 'mesh.domainConnectedAlert',
319+
default: 'Mesh V2 is connected. To change the domain, please disconnect first.'
320+
}));
321+
return;
322+
}
323+
this.props.onOpenMeshDomainModal();
324+
}
306325
handleClickNew () {
307326
// if the project is dirty, and user owns the project, we will autosave.
308327
// but if they are not logged in and can't save, user should consider
@@ -962,6 +981,24 @@ class MenuBar extends React.Component {
962981
<MenuItem onClick={this.handleMeshV2MenuClick}>
963982
{meshV2Status.message}
964983
</MenuItem>
984+
<MenuSection>
985+
<MenuItem onClick={this.handleMeshDomainClick}>
986+
<FormattedMessage
987+
defaultMessage="Domain: {domain}"
988+
description="Label for Mesh V2 domain"
989+
id="mesh.domain"
990+
values={{
991+
domain: this.props.meshV2Domain || (
992+
<FormattedMessage
993+
defaultMessage="Not set"
994+
description="Label for Mesh V2 domain not set"
995+
id="mesh.domainNotSet"
996+
/>
997+
)
998+
}}
999+
/>
1000+
</MenuItem>
1001+
</MenuSection>
9651002
</MenuBarMenu>
9661003
</div>
9671004
);
@@ -1342,6 +1379,7 @@ MenuBar.propTypes = {
13421379
locale: PropTypes.string.isRequired,
13431380
loginMenuOpen: PropTypes.bool,
13441381
logo: PropTypes.string,
1382+
meshV2Domain: PropTypes.string,
13451383
meshV2MenuOpen: PropTypes.bool,
13461384
mode1920: PropTypes.bool,
13471385
mode1990: PropTypes.bool,
@@ -1376,6 +1414,7 @@ MenuBar.propTypes = {
13761414
onOpenRegistration: PropTypes.func,
13771415
onOpenBlockDisplayModal: PropTypes.func,
13781416
onOpenConnectionModal: PropTypes.func,
1417+
onOpenMeshDomainModal: PropTypes.func,
13791418
onOpenDebugModal: PropTypes.func,
13801419
onOpenKoshienTestModal: PropTypes.func,
13811420
onProjectTelemetryEvent: PropTypes.func,
@@ -1399,6 +1438,7 @@ MenuBar.propTypes = {
13991438
onStartSavingToGoogleDrive: PropTypes.func,
14001439
onSaveDirectlyToGoogleDrive: PropTypes.func,
14011440
onSetAiSaveStatus: PropTypes.func,
1441+
onSetMeshV2Domain: PropTypes.func,
14021442
onClearAiSaveStatus: PropTypes.func,
14031443
onStartSelectingUrlLoad: PropTypes.func,
14041444
projectFilename: PropTypes.string,
@@ -1431,6 +1471,7 @@ const mapStateToProps = (state, ownProps) => {
14311471
fileMenuOpen: fileMenuOpen(state),
14321472
editMenuOpen: editMenuOpen(state),
14331473
koshienMenuOpen: koshienMenuOpen(state),
1474+
meshV2Domain: state.scratchGui.meshV2.domain,
14341475
meshV2MenuOpen: meshV2MenuOpen(state),
14351476
extensionLoadCounter: state.scratchGui.koshienFile.extensionLoadCounter,
14361477
aiSaveStatus: state.scratchGui.koshienFile.aiSaveStatus,
@@ -1465,6 +1506,7 @@ const mapDispatchToProps = dispatch => ({
14651506
dispatch(setConnectionModalExtensionId(id));
14661507
dispatch(openConnectionModal());
14671508
},
1509+
onOpenMeshDomainModal: () => dispatch(openMeshDomainModal()),
14681510
onOpenBlockDisplayModal: () => dispatch(openBlockDisplayModal()),
14691511
onOpenKoshienTestModal: () => dispatch(openKoshienTestModal()),
14701512
onClickAccount: () => dispatch(openAccountMenu()),
@@ -1493,6 +1535,7 @@ const mapDispatchToProps = dispatch => ({
14931535
onClickSave: () => dispatch(manualUpdateProject()),
14941536
onClickSaveAsCopy: () => dispatch(saveProjectAsCopy()),
14951537
onExtensionLoaded: () => dispatch(incrementExtensionLoad()),
1538+
onSetMeshV2Domain: domain => dispatch(setMeshV2Domain(domain)),
14961539
onSetAiSaveStatus: status => dispatch(setAiSaveStatus(status)),
14971540
onClearAiSaveStatus: () => dispatch(clearAiSaveStatus()),
14981541
onSeeCommunity: () => dispatch(setPlayer(true)),
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
.modal-content {
2+
width: 480px;
3+
}
4+
5+
.header {
6+
background-color: #00bcd4;
7+
}
8+
9+
.body {
10+
padding: 1.5rem;
11+
display: flex;
12+
flex-direction: column;
13+
}
14+
15+
.input-section {
16+
margin-bottom: 1.5rem;
17+
}
18+
19+
.domain-input {
20+
width: 100%;
21+
padding: 0.75rem;
22+
border: 1px solid #ccc;
23+
border-radius: 0.25rem;
24+
font-size: 1rem;
25+
outline: none;
26+
}
27+
28+
.domain-input:focus {
29+
border-color: #00bcd4;
30+
box-shadow: 0 0 0 2px rgba(0, 188, 212, 0.2);
31+
}
32+
33+
.button-section {
34+
display: flex;
35+
justify-content: flex-end;
36+
gap: 0.75rem;
37+
}
38+
39+
.save-button, .cancel-button {
40+
padding: 0.5rem 1.5rem;
41+
border-radius: 0.25rem;
42+
font-size: 0.875rem;
43+
font-weight: bold;
44+
cursor: pointer;
45+
border: none;
46+
}
47+
48+
.save-button {
49+
background-color: #00bcd4;
50+
color: white;
51+
}
52+
53+
.save-button:hover {
54+
background-color: #0097a7;
55+
}
56+
57+
.cancel-button {
58+
background-color: #f5f5f5;
59+
color: #333;
60+
}
61+
62+
.cancel-button:hover {
63+
background-color: #e0e0e0;
64+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
import bindAll from 'lodash.bindall';
4+
import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl';
5+
6+
import Box from '../box/box.jsx';
7+
import Modal from '../../containers/modal.jsx';
8+
9+
import styles from './mesh-domain-modal.css';
10+
11+
const messages = defineMessages({
12+
title: {
13+
defaultMessage: 'Mesh V2 Domain Settings',
14+
description: 'Title for the Mesh V2 domain modal',
15+
id: 'mesh.domainModalTitle'
16+
},
17+
domainPlaceholder: {
18+
defaultMessage: 'Enter domain name...',
19+
description: 'Placeholder text for domain input field',
20+
id: 'mesh.domainPlaceholder'
21+
},
22+
saveButton: {
23+
defaultMessage: 'Save',
24+
description: 'Label for save button',
25+
id: 'mesh.domainSaveButton'
26+
},
27+
cancelButton: {
28+
defaultMessage: 'Cancel',
29+
description: 'Label for cancel button',
30+
id: 'mesh.domainCancelButton'
31+
}
32+
});
33+
34+
class MeshDomainModal extends React.Component {
35+
constructor (props) {
36+
super(props);
37+
bindAll(this, [
38+
'handleDomainChange',
39+
'handleSaveClick',
40+
'handleCancelClick',
41+
'handleKeyPress'
42+
]);
43+
44+
this.state = {
45+
domain: props.initialDomain || ''
46+
};
47+
}
48+
49+
handleDomainChange (event) {
50+
this.setState({
51+
domain: event.target.value
52+
});
53+
}
54+
55+
handleSaveClick () {
56+
this.props.onSave(this.state.domain.trim());
57+
}
58+
59+
handleCancelClick () {
60+
this.props.onRequestClose();
61+
}
62+
63+
handleKeyPress (event) {
64+
if (event.key === 'Enter') {
65+
this.handleSaveClick();
66+
}
67+
}
68+
69+
render () {
70+
const {intl, onRequestClose} = this.props;
71+
const {domain} = this.state;
72+
73+
return (
74+
<Modal
75+
className={styles.modalContent}
76+
contentLabel={intl.formatMessage(messages.title)}
77+
headerClassName={styles.header}
78+
id="meshDomainModal"
79+
onRequestClose={onRequestClose}
80+
>
81+
<Box className={styles.body}>
82+
<Box className={styles.inputSection}>
83+
<input
84+
className={styles.domainInput}
85+
type="text"
86+
value={domain}
87+
placeholder={intl.formatMessage(messages.domainPlaceholder)}
88+
onChange={this.handleDomainChange}
89+
onKeyPress={this.handleKeyPress}
90+
autoFocus
91+
/>
92+
</Box>
93+
94+
<Box className={styles.buttonSection}>
95+
<button
96+
className={styles.cancelButton}
97+
onClick={this.handleCancelClick}
98+
>
99+
<FormattedMessage
100+
{...messages.cancelButton}
101+
/>
102+
</button>
103+
<button
104+
className={styles.saveButton}
105+
onClick={this.handleSaveClick}
106+
>
107+
<FormattedMessage
108+
{...messages.saveButton}
109+
/>
110+
</button>
111+
</Box>
112+
</Box>
113+
</Modal>
114+
);
115+
}
116+
}
117+
118+
MeshDomainModal.propTypes = {
119+
intl: intlShape.isRequired,
120+
onRequestClose: PropTypes.func.isRequired,
121+
onSave: PropTypes.func.isRequired,
122+
initialDomain: PropTypes.string
123+
};
124+
125+
export default injectIntl(MeshDomainModal);

src/containers/gui.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ const mapStateToProps = state => {
181181
telemetryModalVisible: state.scratchGui.modals.telemetryModal,
182182
rubyTabVisible: state.scratchGui.editorTab.activeTabIndex === RUBY_TAB_INDEX,
183183
urlLoaderModalVisible: state.scratchGui.modals.urlLoaderModal,
184+
meshDomainModalVisible: state.scratchGui.modals.meshDomainModal,
184185
koshienTestModalVisible: state.scratchGui.modals.koshienTestModal,
185186
vm: state.scratchGui.vm
186187
};

0 commit comments

Comments
 (0)