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
3 changes: 3 additions & 0 deletions .claude/rules/scratch-gui/smalruby-markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ upstream ファイルに追加した Smalruby 固有コードのマーカー一
| `src/components/gui/gui.jsx` | meshV2 classroom binding | クラス状態に応じて Mesh v2 ドメインを参加コードに固定する常時マウントの binding |
| `src/components/gui/gui.jsx` | welcome modal | 初回訪問者向けウェルカムモーダル HOC の import と配置、`onShowWelcomeModal` prop |
| `src/containers/gui.jsx` | welcome modal | `onShowWelcomeModal` を Redux `openWelcomeModal()` にマップ |
| `src/components/gui/gui.jsx` | DNCL mode notice | DnclModeNotice コンポーネントの import・配置・`onRequestExitDnclMode` prop |
| `src/containers/gui.jsx` | DNCL mode notice | `onRequestExitDnclMode` を Redux `setDnclMode(false)` + `requestExternalExitDnclMode()` にマップ |
| `src/components/gui/gui.jsx` | Redux action props prevention | Redux action props の伝播防止 |
| `src/components/gui/gui.jsx` | iPad portrait narrow desktop stage size | 744〜1023px viewport で stage を small に強制 (issue #572 Phase 3-C, #599 で 768→744 拡張) |
| `src/components/gui/gui.css` | iPad portrait narrow desktop layout | 744〜1023px viewport で editor-wrapper の flex-basis を緩める (issue #572 Phase 3-C, #599 で 768→744 拡張) |
Expand Down Expand Up @@ -99,6 +101,7 @@ upstream ファイルに追加した Smalruby 固有コードのマーカー一
| `src/containers/menu.jsx` | iPad menu item click fix | メニュー項目クリック時の `setTimeout` 遅延を 0 → 100ms に拡大。iPadOS Safari は `pointerup` から `click` 発火まで ~16–32ms 程度のラグがあり、setTimeout(0) で close すると `<li>` が click 発火前に unmount され React onClick が skip される問題への対応 |
| `src/containers/blocks.jsx` | palette-toggle initial render | `componentDidMount` 末尾で `_applyPaletteVisibility` を呼び `forceUpdate()` を起動。`this.workspace` はインスタンス変数なので `inject()` 後に再レンダーが走らず、初回 `render()` で workspace=null のまま PaletteToggle がスキップされる問題への対応 (issue #695) |
| `src/containers/blocks.jsx` | iOS flyout touch bleed fix | MobileGui (SP) で「ブロックを作る」タップ時に iOS の SVG タッチイベントが「変数を作る」にも伝播する問題の修正。`handlePromptStart` を 50ms 遅延して `externalProcedureDefCallback` が先に呼ばれた場合にキャンセル (issue #698) |
| `src/containers/blocks.jsx` | DNCL block filtering | `shouldComponentUpdate` に `dnclMode` を追加して日本語モード切り替え時に即時再レンダリングを保証。import・`getToolboxXML` 内フィルター・`mapStateToProps` への `dnclMode` 追加も含む |

## 関連ファイル

Expand Down
4 changes: 4 additions & 0 deletions .claude/rules/scratch-gui/smalruby-prettier-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ upstream (Scratch) ファイルは対象外。

**Smalruby 固有ディレクトリ(ディレクトリ内の全ファイルが対象):**
- `src/components/auto-correct-modal/`
- `src/components/dncl-mode-notice/`
- `src/components/block-display-modal/`
- `src/components/classroom-modal/`
- `src/components/classroom-teacher-modal/`
Expand Down Expand Up @@ -204,6 +205,8 @@ upstream (Scratch) ファイルは対象外。
- `test/integration/version-update-notification.test.js`
- `test/integration/workspace-glow-regression.test.js`
- `test/unit/components/action-menu.test.jsx`
- `test/unit/components/dncl-mode-notice.test.jsx`
- `test/unit/components/extension-button-dncl.test.jsx`
- `test/unit/components/connected-step.test.jsx`
- `test/unit/components/mobile-bottom-tabs.test.jsx`
- `test/unit/components/mobile-drawer.test.jsx`
Expand Down Expand Up @@ -284,6 +287,7 @@ upstream (Scratch) ファイルは対象外。
- `test/unit/make-toolbox-xml-hex.test.js`
- `test/unit/only-blocks-initialization.test.js`
- `test/unit/reducers/cards_reducer.test.js`
- `test/unit/reducers/dncl-mode-reducer.test.js`
- `test/unit/reducers/classroom-reducer.test.js`
- `test/unit/reducers/menus-reducer.test.js`
- `test/unit/reducers/palette-visibility-reducer.test.js`
Expand Down
4 changes: 4 additions & 0 deletions packages/scratch-gui/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ src/components/*
!src/components/smalrubot-firmware-modal/
!src/components/blocks-screenshot-button/
!src/components/google-drive-save-dialog/
!src/components/dncl-mode-notice/
!src/components/koshien-test-modal/
!src/components/mobile-bottom-tabs/
!src/components/mobile-drawer/
Expand Down Expand Up @@ -267,6 +268,8 @@ test/unit/*
# Level 3: test/unit/components/*
test/unit/components/*
!test/unit/components/action-menu.test.jsx
!test/unit/components/dncl-mode-notice.test.jsx
!test/unit/components/extension-button-dncl.test.jsx
!test/unit/components/connected-step.test.jsx
!test/unit/components/mobile-bottom-tabs.test.jsx
!test/unit/components/mobile-drawer.test.jsx
Expand Down Expand Up @@ -362,6 +365,7 @@ test/unit/lib/*
# Level 3: test/unit/reducers/*
test/unit/reducers/*
!test/unit/reducers/cards_reducer.test.js
!test/unit/reducers/dncl-mode-reducer.test.js
!test/unit/reducers/classroom-reducer.test.js
!test/unit/reducers/menus-reducer.test.js
!test/unit/reducers/palette-visibility-reducer.test.js
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.notice {
position: absolute;
top: 0;
right: 0;
z-index: 10;
display: flex;
align-items: center;
padding: 6px 12px;
background-color: rgba(255, 243, 205, 0.95);
border-bottom: 1px solid #ffc107;
border-left: 1px solid #ffc107;
font-size: 12px;
gap: 8px;
pointer-events: auto;
}

.message {
color: #856404;
font-weight: 500;
flex: 1;
}

.exitButton {
background: none;
border: 1px solid #856404;
border-radius: 4px;
color: #856404;
cursor: pointer;
font-size: 11px;
padding: 3px 8px;
white-space: nowrap;
flex-shrink: 0;
}

.exitButton:hover {
background-color: #ffc107;
color: #fff;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import intlShape from '../../lib/intlShape.js';
import styles from './dncl-mode-notice.css';

const messages = defineMessages({
message: {
id: 'gui.dnclModeNotice.message',
description: 'Notice shown in blocks tab when DNCL mode is active',
defaultMessage: 'Japanese mode: blocks are restricted',
},
exitButton: {
id: 'gui.dnclModeNotice.exitButton',
description: 'Button label to exit DNCL mode from the blocks tab notice',
defaultMessage: 'Return to Ruby furigana mode',
},
exitConfirm: {
id: 'gui.extensionButton.dnclExtensionConfirm',
description: 'Confirm dialog when switching out of DNCL mode',
defaultMessage:
'Extensions are not available in Japanese mode.\nReturn to Ruby furigana mode to enable extensions.\nSwitch now?',
},
});

const DnclModeNotice = ({ dnclMode, onExitDnclMode, intl }) => {
const handleExitClick = useCallback(() => {
// eslint-disable-next-line no-alert
const confirmed = window.confirm(intl.formatMessage(messages.exitConfirm));
if (confirmed) onExitDnclMode?.();
}, [onExitDnclMode, intl]);

if (!dnclMode) return null;

return (
<div className={styles.notice} data-testid="dncl-mode-notice">
<span className={styles.message}>{intl.formatMessage(messages.message)}</span>
<button
className={styles.exitButton}
data-testid="dncl-mode-notice-exit-button"
onClick={handleExitClick}
>
{intl.formatMessage(messages.exitButton)}
</button>
</div>
);
};

DnclModeNotice.propTypes = {
dnclMode: PropTypes.bool,
intl: intlShape.isRequired,
onExitDnclMode: PropTypes.func,
};

export default injectIntl(DnclModeNotice);
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,38 @@ const messages = defineMessages({
description: 'Button to add an extension in the target pane',
defaultMessage: 'Add Extension'
},
// === Smalruby: Start of DNCL extension alert ===
dnclExtensionDisabled: {
id: 'gui.extensionButton.dnclExtensionDisabled',
description: 'Alert message when extension button is clicked in DNCL mode',
defaultMessage: 'Extensions are not available in Japanese mode.'
// === Smalruby: Start of DNCL extension confirm ===
dnclExtensionConfirm: {
id: 'gui.extensionButton.dnclExtensionConfirm',
description: 'Confirm dialog when extension button is clicked in DNCL mode',
defaultMessage: 'Extensions are not available in Japanese mode.\nReturn to Ruby furigana mode to enable extensions.\nSwitch now?'
}
// === Smalruby: End of DNCL extension alert ===
// === Smalruby: End of DNCL extension confirm ===
});

const ExtensionButton = props => {
const {
intl,
dnclMode,
onExtensionButtonClick
onExtensionButtonClick,
onRequestExitDnclMode
} = props;
const {captureFocus} = useContext(ModalFocusContext);

const handleExtensionButtonClick = useCallback(() => {
// === Smalruby: Start of DNCL extension alert ===
// === Smalruby: Start of DNCL extension confirm ===
if (dnclMode) {
window.alert(intl.formatMessage(messages.dnclExtensionDisabled)); // eslint-disable-line no-alert
// eslint-disable-next-line no-alert
const confirmed = window.confirm(intl.formatMessage(messages.dnclExtensionConfirm));
if (!confirmed) return;
onRequestExitDnclMode?.();
onExtensionButtonClick?.();
return;
}
// === Smalruby: End of DNCL extension alert ===
// === Smalruby: End of DNCL extension confirm ===
captureFocus();
onExtensionButtonClick?.();
}, [captureFocus, onExtensionButtonClick, dnclMode, intl]);
}, [captureFocus, onExtensionButtonClick, onRequestExitDnclMode, dnclMode, intl]);

return (
<Box className={styles.extensionButtonContainer}>
Expand All @@ -68,7 +73,8 @@ const ExtensionButton = props => {
ExtensionButton.propTypes = {
dnclMode: PropTypes.bool,
intl: intlShape.isRequired,
onExtensionButtonClick: PropTypes.func
onExtensionButtonClick: PropTypes.func,
onRequestExitDnclMode: PropTypes.func
};

const ExtensionButtonIntl = injectIntl(ExtensionButton);
Expand Down
12 changes: 12 additions & 0 deletions packages/scratch-gui/src/components/gui/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import Watermark from '../../containers/watermark.jsx';

import Backpack from '../../containers/backpack.jsx';
import ExtensionsButton from '../extension-button/extension-button.jsx';
// === Smalruby: Start of DNCL mode notice ===
import DnclModeNotice from '../dncl-mode-notice/dncl-mode-notice.jsx';
// === Smalruby: End of DNCL mode notice ===
import WebGlModal from '../../containers/webgl-modal.jsx';
import TipsLibrary from '../../containers/tips-library.jsx';
import Cards from '../../containers/cards.jsx';
Expand Down Expand Up @@ -252,6 +255,7 @@ const GUIComponent = props => {
onSetPlatform,
onSetTheme,
onOpenClassroomModal,
onRequestExitDnclMode,
// === Smalruby: End of Redux action props prevention ===
rubyTabVisible,
showComingSoon,
Expand Down Expand Up @@ -670,12 +674,19 @@ const GUIComponent = props => {
vm={vm}
colorMode={colorMode}
/>
{/* === Smalruby: Start of DNCL mode notice === */}
<DnclModeNotice
dnclMode={dnclMode}
onExitDnclMode={onRequestExitDnclMode}
/>
{/* === Smalruby: End of DNCL mode notice === */}
</Box>
{/* === Smalruby: Start of DNCL extension button === */}
<ExtensionsButton
intl={intl}
dnclMode={dnclMode}
onExtensionButtonClick={onExtensionButtonClick}
onRequestExitDnclMode={onRequestExitDnclMode}
/>
{/* === Smalruby: End of DNCL extension button === */}
<Box className={styles.watermark}>
Expand Down Expand Up @@ -835,6 +846,7 @@ GUIComponent.propTypes = {
onClickLogo: PropTypes.func,
onCloseAccountNav: PropTypes.func,
onExtensionButtonClick: PropTypes.func,
onRequestExitDnclMode: PropTypes.func, // === Smalruby: DNCL mode notice ===
onLogOut: PropTypes.func,
onNewSpriteClick: PropTypes.func,
onNewLibraryCostumeClick: PropTypes.func,
Expand Down
3 changes: 2 additions & 1 deletion packages/scratch-gui/src/containers/blocks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ class Blocks extends React.Component {
this.props.stageSize !== nextProps.stageSize ||
this.props.selectedBlocks !== nextProps.selectedBlocks ||
this.props.tutorialAllowedBlocks !== nextProps.tutorialAllowedBlocks ||
this.props.paletteVisible !== nextProps.paletteVisible
this.props.paletteVisible !== nextProps.paletteVisible ||
this.props.dnclMode !== nextProps.dnclMode // === Smalruby: DNCL block filtering ===
);
}
componentDidUpdate (prevProps) {
Expand Down
10 changes: 10 additions & 0 deletions packages/scratch-gui/src/containers/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ import {setPlatform} from '../reducers/platform';
import {setTheme} from '../reducers/settings';
import {setDynamicAssets} from '../reducers/dynamic-assets';
import {showAlertWithTimeout} from '../reducers/alerts';
import { // === Smalruby: DNCL mode notice ===
setDnclMode,
requestExternalExitDnclMode,
} from '../reducers/dncl-mode'; // === Smalruby: DNCL mode notice ===
import {highlightTarget} from '../reducers/targets';
import {
rubyCodeShape,
Expand Down Expand Up @@ -328,6 +332,12 @@ const mapStateToProps = (state, ownProps) => {

const mapDispatchToProps = dispatch => ({
onExtensionButtonClick: () => dispatch(openExtensionLibrary()),
// === Smalruby: Start of DNCL mode notice ===
onRequestExitDnclMode: () => {
dispatch(setDnclMode(false));
dispatch(requestExternalExitDnclMode());
},
// === Smalruby: End of DNCL mode notice ===
onActivateTab: tab => dispatch(activateTab(tab)),
onUpdateDynamicAssets: dynamicAssets => dispatch(setDynamicAssets(dynamicAssets)),
onActivateCostumesTab: () => dispatch(activateTab(COSTUMES_TAB_INDEX)),
Expand Down
23 changes: 17 additions & 6 deletions packages/scratch-gui/src/containers/ruby-tab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import RubyToBlocksConverterHOC from '../lib/ruby-to-blocks-converter-hoc.jsx';
import { containsV1Code } from '../lib/ruby-to-blocks-converter/v1-detection';
import { getUrlParams } from '../lib/url-params';
import { showAlertWithTimeout, closeAlertWithId } from '../reducers/alerts';
import { setDnclMode as setDnclModeAction } from '../reducers/dncl-mode';
import { setDnclMode as setDnclModeAction, clearExternalExitDnclModeRequest } from '../reducers/dncl-mode';
import { BLOCKS_TAB_INDEX, RUBY_TAB_INDEX } from '../reducers/editor-tab';
import { setAiSaveStatus, clearAiSaveStatus } from '../reducers/koshien-file';
import { closeFileMenu } from '../reducers/menus.js';
Expand Down Expand Up @@ -123,6 +123,8 @@ const RubyTab = (props) => {
v1PromptDismissed,
onDismissV1Prompt,
onSetDnclMode,
exitDnclModeExternallyRequested,
onClearExitDnclModeRequest,
} = props;

// --- State ---
Expand Down Expand Up @@ -1161,12 +1163,17 @@ const RubyTab = (props) => {
updateRubyCodeTargetState(vm.editingTarget, rubyVersion);
// Schedule DNCL switch after React re-renders with the new
// Ruby code and the Monaco editor value prop is committed.
// Skip if an external exit was requested (e.g. from extension button).
if (wasDncl) {
requestAnimationFrame(() => {
setTimeout(() => {
handleToggleDnclMode();
}, 0);
});
if (exitDnclModeExternallyRequested) {
onClearExitDnclModeRequest?.();
} else {
requestAnimationFrame(() => {
setTimeout(() => {
handleToggleDnclMode();
}, 0);
});
}
}
}
}
Expand Down Expand Up @@ -1328,6 +1335,8 @@ RubyTab.propTypes = {
v1PromptDismissed: PropTypes.bool,
onDismissV1Prompt: PropTypes.func,
onSetDnclMode: PropTypes.func,
exitDnclModeExternallyRequested: PropTypes.bool,
onClearExitDnclModeRequest: PropTypes.func,
};

const mapStateToProps = (state) => ({
Expand All @@ -1340,6 +1349,7 @@ const mapStateToProps = (state) => ({
locale: state.locales.locale,
activeTabIndex: state.scratchGui.editorTab.activeTabIndex,
v1PromptDismissed: state.scratchGui.settings.v1PromptDismissed,
exitDnclModeExternallyRequested: state.scratchGui.dnclMode.exitDnclModeExternallyRequested,
});

const mapDispatchToProps = (dispatch) => ({
Expand All @@ -1359,6 +1369,7 @@ const mapDispatchToProps = (dispatch) => ({
onMarkRubyTabUsed: () => dispatch(markRubyTabUsed()),
onDismissV1Prompt: () => dispatch(dismissV1Prompt()),
onSetDnclMode: (dnclMode) => dispatch(setDnclModeAction(dnclMode)),
onClearExitDnclModeRequest: () => dispatch(clearExternalExitDnclModeRequest()),
});

const ConnectedRubyTab = RubyteeModalHOC(
Expand Down
5 changes: 4 additions & 1 deletion packages/scratch-gui/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,10 @@ export default {
'gui.menuBar.tutorialTooltip': 'Try Ruby!',
'gui.welcomeTooltip.label': 'Welcome to Smalruby',
'gui.aria.clearButton': 'Clear',
'gui.extensionButton.dnclExtensionDisabled': 'Extensions are not available in Japanese mode.',
'gui.extensionButton.dnclExtensionConfirm':
'Extensions are not available in Japanese mode.\nReturn to Ruby furigana mode to enable extensions.\nSwitch now?',
'gui.dnclModeNotice.message': 'Japanese mode: blocks are restricted.',
'gui.dnclModeNotice.exitButton': 'Return to Ruby furigana mode',
'gui.rubyTab.dnclValidationError':
'This code contains constructs not supported in Japanese mode.\nPlease use only supported instructions before switching modes.',
'gui.mobile.drawer.title': 'Menu',
Expand Down
5 changes: 4 additions & 1 deletion packages/scratch-gui/src/locales/ja-Hira.js
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,10 @@ export default {
'gui.menuBar.tutorialTooltip': 'ルビーをためしてみよう!',
'gui.welcomeTooltip.label': 'スモウルビーへようこそ',
'gui.aria.clearButton': 'クリア',
'gui.extensionButton.dnclExtensionDisabled': 'にほんごモードではかくちょうきのうはつかえません。',
'gui.extensionButton.dnclExtensionConfirm':
'にほんごモードではかくちょうきのうはつかえません。\nRubyふりがなモードにもどすとかくちょうきのうがつかえるようになります。\nもどしますか?',
'gui.dnclModeNotice.message': 'にほんごモード:ブロックがせいげんされています',
'gui.dnclModeNotice.exitButton': 'Rubyふりがなモードにもどす',
'gui.rubyTab.dnclValidationError':
'にほんごモードではたいおうしていないきじゅつです。\nたいおうしているめいれいのみにしてから、モードきりかえをおこなってください。',
'gui.mobile.drawer.title': 'メニュー',
Expand Down
Loading
Loading