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

Commit 425048b

Browse files
authored
Merge pull request #508 from smalruby/feature/mesh-v2-configurable-domain
feat: make meshV2 domain configurable from GUI
2 parents cf9f2c2 + 9959c73 commit 425048b

14 files changed

Lines changed: 598 additions & 8 deletions

File tree

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/gui/gui.jsx

Lines changed: 8 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,
@@ -140,6 +142,8 @@ const GUIComponent = props => {
140142
// Exclude Redux-related props from being passed to DOM
141143
setSelectedBlocks: _setSelectedBlocks,
142144
openUrlLoaderModal: _openUrlLoaderModal,
145+
openKoshienTestModal: _openKoshienTestModal,
146+
openMeshDomainModal: _openMeshDomainModal,
143147
...componentProps
144148
} = omit(props, 'dispatch');
145149
if (children) {
@@ -198,6 +202,9 @@ const GUIComponent = props => {
198202
onLoadUrl={onUrlLoaderSubmit}
199203
/>
200204
) : null}
205+
{meshDomainModalVisible ? (
206+
<MeshDomainModal />
207+
) : null}
201208
{koshienTestModalVisible ? (
202209
<KoshienTestModal
203210
onRequestClose={closeKoshienTestModal}
@@ -458,6 +465,7 @@ GUIComponent.propTypes = {
458465
isTotallyNormal: PropTypes.bool,
459466
loading: PropTypes.bool,
460467
logo: PropTypes.string,
468+
meshDomainModalVisible: PropTypes.bool,
461469
onActivateCostumesTab: PropTypes.func,
462470
onActivateRubyTab: PropTypes.func,
463471
onActivateSoundsTab: PropTypes.func,

src/components/menu-bar/menu-bar.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,12 @@
257257
.save-status-spinner {
258258
flex-shrink: 0;
259259
}
260+
261+
.mesh-v2-domain {
262+
display: inline-block;
263+
vertical-align: bottom;
264+
max-width: 120px;
265+
overflow: hidden;
266+
text-overflow: ellipsis;
267+
white-space: nowrap;
268+
}

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

Lines changed: 72 additions & 5 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,6 +234,7 @@ class MenuBar extends React.Component {
226234
'handleExtensionAdded',
227235
'handleClickKoshienEntryForm',
228236
'handleMeshV2MenuClick',
237+
'handleMeshDomainClick',
229238
'handleClickLearn'
230239
]);
231240
}
@@ -239,6 +248,13 @@ class MenuBar extends React.Component {
239248
this.props.vm.runtime.on('PERIPHERAL_DISCONNECTED', this.handleExtensionAdded);
240249
this.props.vm.runtime.on('PERIPHERAL_REQUEST_ERROR', this.handleExtensionAdded);
241250
}
251+
252+
this.syncMeshV2Domain();
253+
}
254+
componentDidUpdate (prevProps) {
255+
if (this.props.extensionLoadCounter !== prevProps.extensionLoadCounter) {
256+
this.syncMeshV2Domain();
257+
}
242258
}
243259
componentWillUnmount () {
244260
document.removeEventListener('keydown', this.handleKeyPress);
@@ -251,6 +267,16 @@ class MenuBar extends React.Component {
251267
this.props.vm.runtime.off('PERIPHERAL_REQUEST_ERROR', this.handleExtensionAdded);
252268
}
253269
}
270+
syncMeshV2Domain () {
271+
const extension = this.props.vm && this.props.vm.runtime &&
272+
this.props.vm.runtime.peripheralExtensions &&
273+
this.props.vm.runtime.peripheralExtensions.meshV2;
274+
if (extension && extension.domain !== this.props.meshV2Domain) {
275+
if (this.props.onSetMeshV2Domain) {
276+
this.props.onSetMeshV2Domain(extension.domain);
277+
}
278+
}
279+
}
254280
handleExtensionAdded () {
255281
// Dispatch Redux action to trigger re-render
256282
if (this.props.onExtensionLoaded) {
@@ -259,7 +285,7 @@ class MenuBar extends React.Component {
259285
}
260286
getMeshV2Status () {
261287
const vm = this.props.vm;
262-
288+
263289
if (!vm) return {loaded: false};
264290

265291
// In Smalruby 3 / Scratch 3, extensionManager is directly on the vm instance
@@ -269,7 +295,7 @@ class MenuBar extends React.Component {
269295
}
270296

271297
const isLoaded = extensionManager.isExtensionLoaded('meshV2');
272-
298+
273299
if (!isLoaded) {
274300
return {loaded: false};
275301
}
@@ -303,6 +329,22 @@ class MenuBar extends React.Component {
303329
// Open connection modal
304330
this.props.onOpenConnectionModal('meshV2');
305331
}
332+
handleMeshDomainClick () {
333+
// Close the Mesh V2 menu
334+
this.props.onRequestCloseMeshV2();
335+
336+
const extension = this.props.vm && this.props.vm.runtime &&
337+
this.props.vm.runtime.peripheralExtensions &&
338+
this.props.vm.runtime.peripheralExtensions.meshV2;
339+
if (extension && (extension.connectionState === 'connected' || extension.connectionState === 'connecting')) {
340+
alert(this.props.intl.formatMessage({ // eslint-disable-line no-alert
341+
id: 'mesh.domainConnectedAlert',
342+
default: 'Mesh V2 is connected. To change the domain, please disconnect first.'
343+
}));
344+
return;
345+
}
346+
this.props.onOpenMeshDomainModal();
347+
}
306348
handleClickNew () {
307349
// if the project is dirty, and user owns the project, we will autosave.
308350
// but if they are not logged in and can't save, user should consider
@@ -959,9 +1001,28 @@ class MenuBar extends React.Component {
9591001
place={this.props.isRtl ? 'left' : 'right'}
9601002
onRequestClose={this.props.onRequestCloseMeshV2}
9611003
>
962-
<MenuItem onClick={this.handleMeshV2MenuClick}>
963-
{meshV2Status.message}
1004+
<MenuItem onClick={this.handleMeshDomainClick}>
1005+
<FormattedMessage
1006+
defaultMessage="Domain: {domain}"
1007+
description="Label for Mesh V2 domain"
1008+
id="mesh.domain"
1009+
values={{
1010+
domain: (
1011+
<span className={styles.meshV2Domain}>
1012+
{this.props.meshV2Domain || this.props.intl.formatMessage({
1013+
id: 'mesh.domainNotSet',
1014+
defaultMessage: 'Not set'
1015+
})}
1016+
</span>
1017+
)
1018+
}}
1019+
/>
9641020
</MenuItem>
1021+
<MenuSection>
1022+
<MenuItem onClick={this.handleMeshV2MenuClick}>
1023+
{meshV2Status.message}
1024+
</MenuItem>
1025+
</MenuSection>
9651026
</MenuBarMenu>
9661027
</div>
9671028
);
@@ -1342,6 +1403,7 @@ MenuBar.propTypes = {
13421403
locale: PropTypes.string.isRequired,
13431404
loginMenuOpen: PropTypes.bool,
13441405
logo: PropTypes.string,
1406+
meshV2Domain: PropTypes.string,
13451407
meshV2MenuOpen: PropTypes.bool,
13461408
mode1920: PropTypes.bool,
13471409
mode1990: PropTypes.bool,
@@ -1376,6 +1438,7 @@ MenuBar.propTypes = {
13761438
onOpenRegistration: PropTypes.func,
13771439
onOpenBlockDisplayModal: PropTypes.func,
13781440
onOpenConnectionModal: PropTypes.func,
1441+
onOpenMeshDomainModal: PropTypes.func,
13791442
onOpenDebugModal: PropTypes.func,
13801443
onOpenKoshienTestModal: PropTypes.func,
13811444
onProjectTelemetryEvent: PropTypes.func,
@@ -1399,6 +1462,7 @@ MenuBar.propTypes = {
13991462
onStartSavingToGoogleDrive: PropTypes.func,
14001463
onSaveDirectlyToGoogleDrive: PropTypes.func,
14011464
onSetAiSaveStatus: PropTypes.func,
1465+
onSetMeshV2Domain: PropTypes.func,
14021466
onClearAiSaveStatus: PropTypes.func,
14031467
onStartSelectingUrlLoad: PropTypes.func,
14041468
projectFilename: PropTypes.string,
@@ -1431,6 +1495,7 @@ const mapStateToProps = (state, ownProps) => {
14311495
fileMenuOpen: fileMenuOpen(state),
14321496
editMenuOpen: editMenuOpen(state),
14331497
koshienMenuOpen: koshienMenuOpen(state),
1498+
meshV2Domain: state.scratchGui.meshV2 ? state.scratchGui.meshV2.domain : null,
14341499
meshV2MenuOpen: meshV2MenuOpen(state),
14351500
extensionLoadCounter: state.scratchGui.koshienFile.extensionLoadCounter,
14361501
aiSaveStatus: state.scratchGui.koshienFile.aiSaveStatus,
@@ -1465,6 +1530,7 @@ const mapDispatchToProps = dispatch => ({
14651530
dispatch(setConnectionModalExtensionId(id));
14661531
dispatch(openConnectionModal());
14671532
},
1533+
onOpenMeshDomainModal: () => dispatch(openMeshDomainModal()),
14681534
onOpenBlockDisplayModal: () => dispatch(openBlockDisplayModal()),
14691535
onOpenKoshienTestModal: () => dispatch(openKoshienTestModal()),
14701536
onClickAccount: () => dispatch(openAccountMenu()),
@@ -1493,6 +1559,7 @@ const mapDispatchToProps = dispatch => ({
14931559
onClickSave: () => dispatch(manualUpdateProject()),
14941560
onClickSaveAsCopy: () => dispatch(saveProjectAsCopy()),
14951561
onExtensionLoaded: () => dispatch(incrementExtensionLoad()),
1562+
onSetMeshV2Domain: domain => dispatch(setMeshV2Domain(domain)),
14961563
onSetAiSaveStatus: status => dispatch(setAiSaveStatus(status)),
14971564
onClearAiSaveStatus: () => dispatch(clearAiSaveStatus()),
14981565
onSeeCommunity: () => dispatch(setPlayer(true)),
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
@import "../../css/colors.css";
2+
@import "../../css/units.css";
3+
4+
.modal-content {
5+
width: 500px;
6+
height: auto;
7+
line-height: 1.75;
8+
}
9+
10+
.header {
11+
background-color: $motion-primary;
12+
}
13+
14+
.body {
15+
background: $ui-white;
16+
padding: 1.5rem;
17+
display: flex;
18+
flex-direction: column;
19+
gap: 1.5rem;
20+
}
21+
22+
.input-section {
23+
display: flex;
24+
flex-direction: column;
25+
}
26+
27+
.domain-input {
28+
width: 100%;
29+
padding: 0.75rem;
30+
border: 2px solid $ui-black-transparent;
31+
border-radius: 0.25rem;
32+
font-size: 0.875rem;
33+
font-family: inherit;
34+
outline: none;
35+
box-sizing: border-box;
36+
}
37+
38+
.domain-input:focus {
39+
border-color: $motion-primary;
40+
box-shadow: 0 0 0 1px $motion-primary;
41+
}
42+
43+
.domain-input.input-error {
44+
border-color: $error-primary;
45+
box-shadow: 0 0 0 1px $error-primary;
46+
}
47+
48+
.domain-input::placeholder {
49+
color: $text-primary-transparent;
50+
}
51+
52+
.error-message {
53+
margin-top: 0.5rem;
54+
font-size: 0.75rem;
55+
color: $error-primary;
56+
line-height: 1.4;
57+
}
58+
59+
.description-section {
60+
display: flex;
61+
flex-direction: column;
62+
}
63+
64+
.description-text {
65+
font-size: 0.75rem;
66+
color: $text-primary;
67+
line-height: 1.5;
68+
}
69+
70+
.example-section {
71+
display: flex;
72+
flex-direction: column;
73+
margin-top: -0.5rem;
74+
}
75+
76+
.example-title {
77+
font-size: 0.75rem;
78+
color: $text-primary;
79+
font-weight: bold;
80+
margin-bottom: 0.25rem;
81+
}
82+
83+
.example-text {
84+
font-size: 0.75rem;
85+
color: $text-primary-transparent;
86+
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
87+
padding-left: 1rem;
88+
position: relative;
89+
}
90+
91+
.example-text::before {
92+
content: "•";
93+
position: absolute;
94+
left: 0;
95+
color: $text-primary-transparent;
96+
}
97+
98+
.button-section {
99+
display: flex;
100+
justify-content: flex-end;
101+
gap: 0.75rem;
102+
margin-top: 0.5rem;
103+
}
104+
105+
.cancel-button,
106+
.save-button {
107+
padding: 0.5rem 1rem;
108+
border: none;
109+
border-radius: 0.25rem;
110+
font-size: 0.875rem;
111+
font-weight: bold;
112+
cursor: pointer;
113+
transition: background-color 0.1s ease;
114+
min-width: 80px;
115+
}
116+
117+
.cancel-button {
118+
background: $ui-white;
119+
color: $text-primary;
120+
border: 1px solid $ui-black-transparent;
121+
}
122+
123+
.cancel-button:hover {
124+
background: $ui-secondary;
125+
}
126+
127+
.cancel-button:active {
128+
background: $ui-black-transparent;
129+
}
130+
131+
.save-button {
132+
background: $motion-primary;
133+
color: $ui-white;
134+
}
135+
136+
.save-button:hover {
137+
background: $motion-tertiary;
138+
}
139+
140+
.save-button:active {
141+
background: $motion-tertiary;
142+
}
143+
144+
.save-button.disabled {
145+
background: $ui-black-transparent;
146+
color: $text-primary-transparent;
147+
cursor: not-allowed;
148+
}

0 commit comments

Comments
 (0)