Skip to content

meshV2: Display connection status in menu bar with group name and expiry time #494

@takaokouji

Description

@takaokouji

Summary

Display group name and expiry time in the menu bar when meshV2 extension is enabled, with visual connection status indicators.

Current State

Currently, the meshV2 extension has no menu bar presence. Connection state is tracked in the extension but not visibly displayed to users.

Connection state tracking (gui/scratch-vm/src/extensions/scratch3_mesh_v2/index.js):

  • Line 57: this.connectionState tracks state ('disconnected', 'scanning', 'connecting', 'connected', 'error')
  • Line 259-287: setConnectionState() method manages state transitions
  • Line 304-338: connectedMessage() generates status message but only shown in connection modal

UI Design

When meshV2 extension is enabled, display in menu bar:

(アイコン) メッシュ ▽
└─ グループ名 (有効期限)

States

  1. Not connected: Display "未接続"
  2. Connected as host: Display "グループ名 (有効期限)"
  3. Connected as member: Display "グループ名 (有効期限)"

Behavior

  • Clicking the menu item opens the meshV2 connection modal
  • Icon changes based on connection state:
    • Connected: meshV2_connected.png
    • Disconnected: meshV2_disconnected.png

Connection Status Icons

Icon Files Location

  • Connected: gui/smalruby3-gui/tmp/meshV2_connected.png (1024x1024)
  • Disconnected: gui/smalruby3-gui/tmp/meshV2_disconnected.png (1024x1024)

Icon Processing Required

Resize to 21x21 pixels and move to appropriate location:

# Using ImageMagick
convert gui/smalruby3-gui/tmp/meshV2_connected.png -resize 21x21 gui/smalruby3-gui/src/components/menu-bar/icon--mesh-connected.png
convert gui/smalruby3-gui/tmp/meshV2_disconnected.png -resize 21x21 gui/smalruby3-gui/src/components/menu-bar/icon--mesh-disconnected.png

Implementation Details

1. Add Menu Bar State Management

File: gui/smalruby3-gui/src/reducers/menus.js

Add meshV2 menu state similar to other menus:

// Add to action types
const OPEN_MESH_V2_MENU = 'scratch-gui/menus/OPEN_MESH_V2_MENU';
const CLOSE_MESH_V2_MENU = 'scratch-gui/menus/CLOSE_MESH_V2_MENU';

// Add to initial state
const initialState = {
    // ... existing state
    meshV2: false
};

// Add to reducer
case OPEN_MESH_V2_MENU:
    return Object.assign({}, state, {
        meshV2: true
    });
case CLOSE_MESH_V2_MENU:
    return Object.assign({}, state, {
        meshV2: false
    });

// Add action creators
const openMeshV2Menu = () => ({type: OPEN_MESH_V2_MENU});
const closeMeshV2Menu = () => ({type: CLOSE_MESH_V2_MENU});
const meshV2MenuOpen = state => state.scratchGui.menus.meshV2;

// Export
export {
    openMeshV2Menu,
    closeMeshV2Menu,
    meshV2MenuOpen
};

2. Add Menu Bar Component

File: gui/smalruby3-gui/src/components/menu-bar/menu-bar.jsx

Import Icons (add near line 100):

import meshConnectedIcon from './icon--mesh-connected.png';
import meshDisconnectedIcon from './icon--mesh-disconnected.png';

Import Menu Actions (add near line 88):

import {
    openMeshV2Menu,
    closeMeshV2Menu,
    meshV2MenuOpen
} from '../../reducers/menus';

Add Method to Get MeshV2 State (add near line 240):

getMeshV2Status () {
    const vm = this.props.vm;
    if (!vm.runtime || !vm.runtime.extensionManager) {
        return {loaded: false};
    }
    
    const isLoaded = vm.runtime.extensionManager.isExtensionLoaded('meshV2');
    if (!isLoaded) {
        return {loaded: false};
    }
    
    // Get extension instance
    const extension = vm.runtime.ext_meshV2;
    if (!extension) {
        return {loaded: true, connected: false};
    }
    
    const isConnected = extension.isConnected();
    const message = extension.connectedMessage();
    const connected = extension.connectionState === 'connected';
    
    return {
        loaded: true,
        connected: connected,
        message: message,
        icon: connected ? meshConnectedIcon : meshDisconnectedIcon
    };
}

handleMeshV2MenuClick () {
    // Open connection modal
    if (this.props.vm.runtime && this.props.vm.runtime.emit) {
        this.props.vm.runtime.emit(
            this.props.vm.runtime.constructor.PERIPHERAL_SCAN_START,
            'meshV2'
        );
    }
}

Add Menu Item in Render (add after koshien menu, around line 970):

{(() => {
    const meshV2Status = this.getMeshV2Status();
    if (!meshV2Status.loaded) return null;
    
    return (
        <div
            className={classNames(styles.menuBarItem, styles.hoverable, {
                [styles.active]: this.props.meshV2MenuOpen
            })}
        >
            <img
                className={styles.meshIcon}
                src={meshV2Status.icon}
            />
            <div
                className={classNames(
                    styles.collapsibleMenuBarItem
                )}
                onMouseUp={this.props.onClickMeshV2}
            >
                <span className={styles.collapsibleLabel}>
                    <FormattedMessage
                        defaultMessage="Mesh"
                        description="Label for Mesh V2 menu"
                        id="gui.menuBar.meshV2"
                    />
                </span>
                <img src={dropdownCaret} />
                <MenuBarMenu
                    className={classNames(styles.menuBarMenu)}
                    open={this.props.meshV2MenuOpen}
                    place={this.props.isRtl ? 'left' : 'right'}
                    onRequestClose={this.props.onRequestCloseMeshV2}
                >
                    <MenuItem onClick={this.handleMeshV2MenuClick}>
                        {meshV2Status.message}
                    </MenuItem>
                </MenuBarMenu>
            </div>
        </div>
    );
})()}

3. Add CSS Styles

File: gui/smalruby3-gui/src/components/menu-bar/menu-bar.css

.mesh-icon {
    width: 21px;
    height: 21px;
    margin-right: 0.5rem;
}

4. Connect Redux State

File: gui/smalruby3-gui/src/components/menu-bar/menu-bar.jsx

Update mapStateToProps (around line 1000+):

const mapStateToProps = state => {
    // ... existing mappings
    meshV2MenuOpen: meshV2MenuOpen(state)
};

Update mapDispatchToProps (around line 1050+):

const mapDispatchToProps = dispatch => ({
    // ... existing dispatches
    onClickMeshV2: () => dispatch(openMeshV2Menu()),
    onRequestCloseMeshV2: () => dispatch(closeMeshV2Menu())
});

5. Update Messages

File: gui/smalruby3-gui/src/locales/ja.js

'gui.menuBar.meshV2': 'メッシュ',
'mesh.notConnected': '未接続'

Extension Connection Modal Integration

The menu item click should trigger the existing connection modal flow:

Current flow (gui/scratch-vm/src/extensions/scratch3_mesh_v2/index.js):

  1. User clicks extension in library → connection modal appears
  2. Extension calls scan() method (line 159-209)
  3. User selects host/group → connect() called (line 212-253)

New flow from menu bar:

  1. User clicks menu bar item → emit PERIPHERAL_SCAN_START event
  2. Same connection modal flow as above

Files to Modify

  1. gui/smalruby3-gui/src/reducers/menus.js

    • Add meshV2 menu state management
  2. gui/smalruby3-gui/src/components/menu-bar/menu-bar.jsx

    • Import icons and actions
    • Add getMeshV2Status() method
    • Add handleMeshV2MenuClick() method
    • Add menu bar item in render
    • Update mapStateToProps and mapDispatchToProps
  3. gui/smalruby3-gui/src/components/menu-bar/menu-bar.css

    • Add .mesh-icon style
  4. gui/smalruby3-gui/src/locales/ja.js (and other locales)

    • Add gui.menuBar.meshV2 message
    • Add mesh.notConnected message (if not exists)
  5. Create icon files:

    • gui/smalruby3-gui/src/components/menu-bar/icon--mesh-connected.png (21x21)
    • gui/smalruby3-gui/src/components/menu-bar/icon--mesh-disconnected.png (21x21)

Testing

  1. Load meshV2 extension
  2. Verify menu bar shows "メッシュ ▽" with disconnected icon
  3. Click menu item → verify "未接続" appears
  4. Click "未接続" → verify connection modal opens
  5. Connect as host → verify icon changes to connected
  6. Verify menu shows "んたしたたに (14:55)" or similar
  7. Disconnect → verify icon returns to disconnected state

Visual Feedback

The icon provides clear visual feedback:

  • Green/Connected icon: User is connected to a mesh network
  • Gray/Disconnected icon: User is not connected

This helps users quickly understand their mesh connection status without opening menus.

Future Enhancements

After April 2026 when legacy mesh is removed:

  • Remove " V2" from all labels
  • This menu bar item becomes the primary mesh interface
  • Legacy mesh menu bar presence (if any) is removed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions