Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/maintenance/smalruby-markers-gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ upstream ファイルに追加した Smalruby 固有コードのマーカー一
| `src/components/menu-bar/menu-bar.jsx` | classroom button | クラスルームボタンの import、レンダリング、Redux 接続 |
| `src/components/menu-bar/menu-bar.jsx` | welcome tooltip | About (`?`) ボタンの左隣にウェルカムバルーンを描画。`buildAboutMenu` 内に `WelcomeTooltip` 配置 + `position: relative` 化、`handleClickWelcomeTooltip` ハンドラ追加、`onShowWelcomeModal` 用 mapDispatchToProps 追加 |
| `src/components/menu-bar/settings-menu.jsx` | classroom management menu | クラス管理メニューアイテムの import、レンダリング、Redux 接続 |
| `src/components/menu-bar/settings-menu.jsx` | display mode menu | 表示モード (自動/PC/スマホ) 切替。テーマ/Ruby と同じ `PreferenceMenu` サブメニュー形式 + 専用アイコン。`useDisplayMode` hook + `persistDisplayMode` の import、ハンドラ、Redux (open/close) 接続、レンダリング (Issue #865) |
| `src/reducers/menus.js` | display mode menu | `PreferenceMenu` サブメニューの開閉用 Redux state (`displayModeMenu`)。定数/rootMenu への登録/initialState/open・close・selector の追加 (Issue #865) |
| `webpack.config.js` | classroom API | CLASSROOM_API_ENDPOINT 環境変数注入 |
| `webpack.config.js` | scratch api proxy endpoint | SCRATCH_API_PROXY_ENDPOINT 環境変数注入 |
| `eslint.config.mjs` | react lifecycle typo detection | `react/no-typos` を error にして getDerivedStateFromProps/Error の static 抜け等を lint で検出 |
Expand Down
24 changes: 18 additions & 6 deletions docs/mobile-ui/playwright.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,20 @@ active 状態は `[data-active="true"]` 属性で表現される。
| `mobile-drawer-toggle-${key}` | button | アコーディオン開閉 (`language` / `ruby-version` など) |
| `mobile-drawer-locale-${code}` | button | locale 選択 (`ja` / `ja-Hira` / `en` ほか) |
| `mobile-drawer-ruby-version-${version}` | button | Ruby version 切替 (`1` / `2`) |
| `mobile-drawer-switch-to-desktop` | button | PC モードに切り替える (表示モードを desktop に固定, #865) |

### 3.3 MobileBottomTabs (`mobile-bottom-tabs.jsx`)
### 3.3 MobileModeNotice (`mobile-mode-notice.jsx`)

スマホモードで一度だけ表示される案内バー (#865)。「いまはスマホ用の画面である / PC とは違う / メニューから PC モードへ切り替えられる」ことを伝える。閉じると `localStorage['smalruby:mobileModeNoticeDismissed']='true'` に記録し以後出さない。

| data-testid | 要素 | 役割 |
| ------------------------------ | ------ | ------------------------------------------------- |
| `mobile-mode-notice` | div | 案内バー本体 (dismiss 済みなら DOM に無い) |
| `mobile-mode-notice-switch` | button | PC モードに切り替える (desktop に固定 + 閉じる) |
| `mobile-mode-notice-dismiss` | button | 案内を閉じる (スマホモードのまま) |
| `mobile-mode-notice-close` | button | × で閉じる (dismiss と同じ) |

### 3.4 MobileBottomTabs (`mobile-bottom-tabs.jsx`)

旧構成で使っていたボトムタブ。現在は MobileSideRail に統合済みで MobileGui からはレンダリングされないが、コンポーネント本体は残っているため data-testid も保持されている。

Expand All @@ -113,7 +125,7 @@ active 状態は `[data-active="true"]` 属性で表現される。
| `mobile-bottom-tabs` | div | ボトムタブコンテナ |
| `mobile-bottom-tabs-${tab.key}` | button | 各タブボタン |

### 3.4 MobileTopBar (`mobile-top-bar.jsx`)
### 3.5 MobileTopBar (`mobile-top-bar.jsx`)

旧フェーズで使っていた上部バー。同じく現在は MobileSideRail 統合済み。

Expand All @@ -124,27 +136,27 @@ active 状態は `[data-active="true"]` 属性で表現される。
| `mobile-top-bar-title` | span | プロジェクト名表示 |
| `mobile-top-bar-play` | button | 実行/停止 (現在 MobileSideRail に移動) |

### 3.5 MobileSpritePanel (`mobile-sprite-panel.jsx`)
### 3.6 MobileSpritePanel (`mobile-sprite-panel.jsx`)

| data-testid | 要素 | 役割 |
| ----------------------- | ---- | ------------------------------ |
| `mobile-sprite-panel` | div | スプライト overlay コンテナ |

中身は upstream `<TargetPane>`。スプライト一覧・追加 FAB・ステージ列は upstream のセレクタで指す。

### 3.6 MobileOrientationGate (`mobile-orientation-gate.jsx`)
### 3.7 MobileOrientationGate (`mobile-orientation-gate.jsx`)

| data-testid | 要素 | 役割 |
| ---------------------------- | ---- | ------------------------ |
| `mobile-orientation-gate` | div | 縦持ち警告オーバーレイ |

### 3.7 MobilePaintToolbarToggle (`mobile-paint-toolbar-toggle.jsx`)
### 3.8 MobilePaintToolbarToggle (`mobile-paint-toolbar-toggle.jsx`)

| data-testid | 要素 | 役割 |
| --------------------------------- | ------ | ------------------------------------- |
| `mobile-paint-toolbar-toggle` | button | コスチュームタブの ▼/▲ トグル |

### 3.8 RubyToolbar (`ruby-toolbar.jsx`)
### 3.9 RubyToolbar (`ruby-toolbar.jsx`)

ルビータブの上部ツールバー。SP/desktop で同じセレクタが使える。

Expand Down
31 changes: 26 additions & 5 deletions docs/mobile-ui/ui-ux.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@

## 1. 切り替えロジック (どのモードがいつ出るか)

切り替えは **viewport ベースで自動**。URL パラメータでのオプトインは設けない。`useIsNarrowScreen` の `matchMedia` がリアルタイムに反応するので、ブラウザリサイズ・端末回転に追従する。
切り替えは **viewport ベースで自動**。URL パラメータでのオプトインは設けない。`useIsNarrowScreen` の `matchMedia` がリアルタイムに反応するので、ブラウザリサイズ・端末回転に追従する。ただし、ユーザーが表示モードを明示指定した場合はそれを優先する (下記「ユーザーによる表示モードの固定」)。

### 切り替え式

`packages/scratch-gui/src/lib/use-is-narrow-screen.js`:

```js
const NARROW_SCREEN_QUERY = '(max-width: 743px), (max-height: 500px)';
const NARROW_SCREEN_QUERY = '(max-width: 743px), (max-width: 950px) and (max-height: 500px)';
```

`packages/scratch-gui/src/lib/responsive-gui.jsx` がこの hook の結果で `<MobileGui>` と `<GUI>` (upstream desktop) を出し分ける。
Expand All @@ -29,17 +29,18 @@ const NARROW_SCREEN_QUERY = '(max-width: 743px), (max-height: 500px)';

| Viewport (例) | 短辺幅 | 高さ | 出るモード | スクリーンショット |
| ------------------------- | -------- | ------- | ------------------------------- | ------------------ |
| iPhone 14 横 (844×390) | 844 | 390 | **MobileGui** (高さ ≤ 500 で発火) | 02〜11 |
| iPhone 14 横 (844×390) | 844 | 390 | **MobileGui** (幅 ≤ 950 かつ 高さ ≤ 500 で発火) | 02〜11 |
| iPhone 14 縦 (390×844) | 390 | 844 | **MobileGui** + 縦持ち警告 | 11 |
| Chromebook ズーム (1380×480) | 1380 | 480 | **desktop GUI** (幅 > 950 なので高さ条件は無効) | — |
| iPad mini portrait (744×1133) | 744 | 1133 | desktop GUI (iPad 調整) | 23 |
| iPad portrait (768×1024) | 768 | 1024 | desktop GUI (iPad 調整) | 20 |
| iPad landscape (1024×768) | 1024 | 768 | desktop GUI (高さ調整あり) | 21、22 |
| Desktop (1280×800) | 1280 | 800 | desktop GUI (upstream 通常) | 24 |

### しきい値の根拠

- **幅 743px** は iPad mini portrait (744) を **MobileGui に落とさない** ための境界。MobileGui は横向き専用 UI なので、iPad portrait のような縦長を強制的に MobileGui で見せると逆に使いにくい。
- **高さ 500px** はスマホ横持ち (390 高さ) を確実に拾う保険。デスクトップで縦を 500 以下に縮めるのは一般的でないので副作用は小さい
- **幅 743px** は iPad mini portrait (744) を **MobileGui に落とさない** ための境界。MobileGui は横向き専用 UI なので、iPad portrait のような縦長を強制的に MobileGui で見せると逆に使いにくい。iPhone 縦持ちのように横が極端に狭い端末をスマホモードにする主条件。
- **高さ 500px は幅 950px 以下に限定する**。スマホ横持ち (390 高さ) を確実に拾いつつ、**Chromebook をズームして高さだけ縮んだ広い画面 (例 1380×480) を誤ってスマホモードにしない** ため (Issue #865)。以前は `(max-height: 500px)` を無条件で OR していたため、Chromebook のズームで高さが 500px 以下になると意図せずスマホモードに入る不具合があった。950px はスマホ横持ちの最大級 (iPhone Pro Max 系 ~932px) を含み、Chromebook / ノート PC の一般的な幅 (≥1280px) を確実に除外する

### iPad モードでの追加調整 (744〜1023px の desktop GUI)

Expand All @@ -64,6 +65,26 @@ const NARROW_SCREEN_QUERY = '(max-width: 743px), (max-height: 500px)';
- **MobileGui は別コンポーネントに分離**: desktop GUI の一部だけを CSS で隠すアプローチでは「右ペインも見えないのに React tree には残る → 余計なリスナーや HMR コスト」が累積するため、MobileGui を独立したコンポーネントにして一度差し替える方式にした。
- **iPad は desktop GUI のまま**: iPad は横幅 768〜1024px 程度あり、MobileGui の縦タブ集約より desktop の従来 UI のほうが学習コストが低い。なので iPad は desktop GUI に CSS/レイアウト調整を当てる方針。

### ユーザーによる表示モードの固定 (Issue #865)

viewport 自動判定に加えて、**ユーザーが表示モードを明示指定して固定できる**。Chromebook のズーム等で意図せずスマホモードに入ってしまったユーザーが、自力で PC モードへ抜け出すための逃げ道。

| モード | 値 | 挙動 |
| ------ | -- | ---- |
| 自動 | `auto` (既定) | viewport 自動判定 (上記の切り替え式) |
| PC モード | `desktop` | viewport に関係なく常に desktop GUI |
| スマホモード | `mobile` | viewport に関係なく常に MobileGui |

- 設定は **localStorage `smalruby:displayMode`** に保存され、そのマシンでは以後ずっと維持される (`auto` はキー削除 = 未設定と等価)。
- `persistDisplayMode()` が `smalruby:displayModeChanged` イベントを発火し、`useDisplayMode` hook 経由で `ResponsiveGui` がリアルタイムに切り替える (リロード不要)。
- **切り替え口**:
- スマホモード中: モバイルドロワー (☰) の目立つ「PCモードに切り替える」ボタン (`mobile-drawer-switch-to-desktop`)。
- どちらのモードでも: 設定メニュー → 「表示モード」。テーマ / Ruby バージョンと同じ **`PreferenceMenu` サブメニュー**形式で、専用アイコン付き。クリックすると 自動 / PCモード / スマホモード のサブメニューが開き、現在値にチェックが付く。
- **スマホモードの気付き**: スマホモードに入ると、一度きりの案内バー **`MobileModeNotice`** (`mobile-mode-notice`) が出て「いまはスマホ用の画面である / PC とは表示が違う / ここから PC モードへ切り替えられる」ことを伝える。閉じると localStorage (`smalruby:mobileModeNoticeDismissed`) に記録し、そのマシンでは以後出さない。§4「PC 表示が崩れている警告バナーを置かない」原則とは異なり、これは *スマホモード側で* 現在の状態と脱出口を知らせる中立的な案内であり、警告ではない。
- 関連ファイル: `src/lib/settings/display-mode/`、`src/lib/use-display-mode.js`、`src/lib/responsive-gui.jsx`、`src/components/mobile-drawer/mobile-drawer.jsx`、`src/components/mobile-mode-notice/mobile-mode-notice.jsx`、`src/components/menu-bar/settings-menu.jsx`、`src/reducers/menus.js`。

> **設計原則との関係**: これは「URL オプトインフラグ」ではなく **UI から操作する永続ユーザー設定** であり、§4「オプトインフラグを増やさない」に反しない。既定はあくまで viewport 自動判定で、明示指定したユーザーだけが固定される。

---

## 2. MobileGui (スマホ横向き)
Expand Down
5 changes: 5 additions & 0 deletions packages/scratch-gui/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ src/components/*
!src/components/mobile-bottom-tabs/
!src/components/mobile-drawer/
!src/components/mobile-gui/
!src/components/mobile-mode-notice/
!src/components/mobile-orientation-gate/
!src/components/mobile-paint-toolbar-toggle/
!src/components/mobile-side-rail/
Expand Down Expand Up @@ -179,6 +180,7 @@ src/lib/*
!src/lib/url-loader.js
!src/lib/url-params.js
!src/lib/url-parser.js
!src/lib/use-display-mode.js
!src/lib/use-is-narrow-screen.js
!src/lib/version-checker.js

Expand Down Expand Up @@ -293,6 +295,7 @@ test/unit/components/*
!test/unit/components/mesh-self-sensor-notice.test.jsx
!test/unit/components/mobile-bottom-tabs.test.jsx
!test/unit/components/mobile-drawer.test.jsx
!test/unit/components/mobile-mode-notice.test.jsx
!test/unit/components/mobile-orientation-gate.test.jsx
!test/unit/components/mobile-side-rail.test.jsx
!test/unit/components/mobile-sprite-panel.test.jsx
Expand Down Expand Up @@ -361,7 +364,9 @@ test/unit/lib/*
!test/unit/lib/make-toolbox-xml.test.js
!test/unit/lib/mesh-v2-classroom-binding.test.js
!test/unit/lib/mesh-v2-sensor-collision.test.js
!test/unit/lib/display-mode-persistence.test.js
!test/unit/lib/responsive-gui.test.jsx
!test/unit/lib/use-display-mode.test.js
!test/unit/lib/use-is-narrow-screen.test.js
!test/unit/lib/module-sync.test.js
!test/unit/lib/prism-parser.test.js
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 54 additions & 2 deletions packages/scratch-gui/src/components/menu-bar/settings-menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import LanguageMenu from './language-menu.jsx';
import MenuBarMenu from './menu-bar-menu.jsx';
import {MenuSection, MenuItem} from '../menu/menu.jsx';
import PreferenceMenu from './preference-menu.jsx';
// === Smalruby: Start of display mode menu ===
import useDisplayMode from '../../lib/use-display-mode.js';
import {displayModeMap, messages as displayModeMessages} from '../../lib/settings/display-mode/index.js';
import {persistDisplayMode} from '../../lib/settings/display-mode/persistence.js';
import displayModeIcon from './icon--display-mode.svg';
// === Smalruby: End of display mode menu ===

import {DEFAULT_MODE, HIGH_CONTRAST_MODE, colorModeMap} from '../../lib/settings/color-mode/index.js';
import {themeMap} from '../../lib/settings/theme/index.js';
Expand Down Expand Up @@ -36,7 +42,11 @@ import {
rubyVersionMenuOpen,
openColorModeMenu,
openThemeMenu,
openRubyVersionMenu
openRubyVersionMenu,
// === Smalruby: Start of display mode menu ===
displayModeMenuOpen,
openDisplayModeMenu
// === Smalruby: End of display mode menu ===
} from '../../reducers/menus.js';

const enabledColorModes = [DEFAULT_MODE, HIGH_CONTRAST_MODE];
Expand All @@ -51,13 +61,15 @@ const SettingsMenu = ({
isColorModeMenuOpen,
isThemeMenuOpen,
isRubyVersionMenuOpen,
isDisplayModeMenuOpen,
activeColorMode,
activeRubyVersion,
onChangeColorMode,
onChangeRubyVersion,
onRequestOpenColorMode,
onRequestOpenTheme,
onRequestOpenRubyVersion,
onRequestOpenDisplayMode,
onOpenBlockDisplayModal,
onOpenTeacherModal,
activeTheme,
Expand All @@ -67,6 +79,17 @@ const SettingsMenu = ({
settingsMenuOpen
}) => {
const intl = useIntl();
// === Smalruby: Start of display mode menu ===
// 表示モード (auto / PC / スマホ) の現在値と切替ハンドラ (Issue #865)。
// 他の設定メニュー (テーマ / Ruby バージョン) と同じ PreferenceMenu の
// サブメニューとして出す。表示モードは Redux ではなく localStorage 駆動
// (useDisplayMode で購読) なので、選択時は persistDisplayMode するだけ。
const activeDisplayMode = useDisplayMode();
const handleChangeDisplayMode = useCallback(mode => {
persistDisplayMode(mode);
onRequestClose();
}, [onRequestClose]);
// === Smalruby: End of display mode menu ===
const enabledColorModesMap = useMemo(() => Object.keys(colorModeMap).reduce((acc, colorMode) => {
if (enabledColorModes.includes(colorMode)) {
acc[colorMode] = colorModeMap[colorMode];
Expand Down Expand Up @@ -176,6 +199,25 @@ const SettingsMenu = ({
onRequestCloseSettings={onRequestClose}
onRequestOpen={onRequestOpenRubyVersion}
/>
{/* === Smalruby: Start of display mode menu === */}
{/*
* 表示モード切替 (Issue #865)。auto / PC / スマホ を選べる。
* テーマ / Ruby バージョンと同じ PreferenceMenu サブメニュー形式。
* Chromebook 等で意図せずスマホモードに入ったユーザーが、ここから
* いつでも PC モードへ固定できる (localStorage に保存)。
*/}
<PreferenceMenu
open={isDisplayModeMenuOpen}
itemsMap={displayModeMap}
onChange={handleChangeDisplayMode}
defaultMenuIconSrc={displayModeIcon}
submenuLabel={displayModeMessages.displayModeMenu}
selectedItemKey={activeDisplayMode}
isRtl={isRtl}
onRequestCloseSettings={onRequestClose}
onRequestOpen={onRequestOpenDisplayMode}
/>
{/* === Smalruby: End of display mode menu === */}
<MenuItem onClick={onOpenBlockDisplayModal}>
<div className={styles.option}>
<img
Expand Down Expand Up @@ -231,6 +273,8 @@ SettingsMenu.propTypes = {
onRequestOpenColorMode: PropTypes.func,
isColorModeMenuOpen: PropTypes.bool,
isRubyVersionMenuOpen: PropTypes.bool,
isDisplayModeMenuOpen: PropTypes.bool,
onRequestOpenDisplayMode: PropTypes.func,
onOpenBlockDisplayModal: PropTypes.func,
onOpenTeacherModal: PropTypes.func,
activeTheme: PropTypes.string,
Expand All @@ -252,7 +296,10 @@ const mapStateToProps = state => ({
activeRubyVersion: state.scratchGui.settings.rubyVersion,
isColorModeMenuOpen: colorModeMenuOpen(state),
isThemeMenuOpen: themeMenuOpen(state),
isRubyVersionMenuOpen: rubyVersionMenuOpen(state)
isRubyVersionMenuOpen: rubyVersionMenuOpen(state),
// === Smalruby: Start of display mode menu ===
isDisplayModeMenuOpen: displayModeMenuOpen(state)
// === Smalruby: End of display mode menu ===
});

const mapDispatchToProps = (dispatch, ownProps) => ({
Expand All @@ -265,6 +312,11 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
onRequestOpenRubyVersion: () => {
dispatch(openRubyVersionMenu());
},
// === Smalruby: Start of display mode menu ===
onRequestOpenDisplayMode: () => {
dispatch(openDisplayModeMenu());
},
// === Smalruby: End of display mode menu ===
onOpenBlockDisplayModal: () => {
ownProps.onOpenBlockDisplayModal();
},
Expand Down
Loading
Loading