From 3b353d16dc338e719d331565fc77a95d7b3406fc Mon Sep 17 00:00:00 2001 From: "pishi.ai" Date: Sun, 13 Apr 2025 19:46:09 +0330 Subject: [PATCH 1/2] fix(gui): rtl support for delete-confirmation-prompt This commit adds RTL (Right-to-Left) support for the delete-confirmation-prompt. In RTL languages, when multiple sprites are opened, the delete section of the confirmation prompt extended beyond the window boundary. This update fixes the layout so it displays correctly in RTL as well as LTR. --- .../delete-confirmation-prompt.css | 32 ++++++++++++++++--- .../delete-confirmation-prompt.jsx | 19 +++++++---- .../sprite-selector/sprite-list.jsx | 8 +++-- .../sprite-selector/sprite-selector.jsx | 1 + .../containers/delete-confirmation-prompt.jsx | 26 +++++++++++++++ .../src/containers/sprite-selector-item.jsx | 9 ++++-- .../containers/sprite-selector-item.test.jsx | 2 +- 7 files changed, 78 insertions(+), 19 deletions(-) create mode 100644 packages/scratch-gui/src/containers/delete-confirmation-prompt.jsx diff --git a/packages/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.css b/packages/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.css index 295bb6b5c3..060b02df3b 100644 --- a/packages/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.css +++ b/packages/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.css @@ -10,17 +10,32 @@ .arrow-container { display: flex; align-items: center; +} + +[dir="ltr"] .arrow-container { margin-right: -7px; } -.arrow-container-left { +[dir="rtl"] .arrow-container { + margin-left: -7px; +} + +[dir="ltr"] .arrow-container-left { margin-right: -7px; } -.arrow-container-right { +[dir="rtl"] .arrow-container-left { margin-left: -7px; } +[dir="ltr"] .arrow-container-right { + margin-left: -7px; +} + +[dir="rtl"] .arrow-container-right { + margin-right: -7px; +} + .body { padding: 1rem 1.5rem; border-radius: 0.5rem; @@ -55,14 +70,22 @@ margin: auto; } -.button-row button.ok-button { +[dir="ltr"] .button-row button.ok-button { margin-left: 0; } -.button-row button.cancel-button { +[dir="rtl"] .button-row button.ok-button { + margin-right: 0; +} + +[dir="ltr"] .button-row button.cancel-button { margin-right: 0; } +[dir="rtl"] .button-row button.cancel-button { + margin-left: 0; +} + .message { margin-top: 0.25rem; } @@ -71,4 +94,3 @@ height: 1.5rem; width: 1.5rem; } - diff --git a/packages/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.jsx b/packages/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.jsx index 2a6eea9525..56cb407b3b 100644 --- a/packages/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.jsx +++ b/packages/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.jsx @@ -85,7 +85,8 @@ const DeleteConfirmationPrompt = ({ onOk, modalPosition, entityType, - relativeElemRef + relativeElemRef, + isRtl }) => { const modalPositionValues = calculateModalPosition(relativeElemRef, modalPosition); @@ -119,12 +120,15 @@ const DeleteConfirmationPrompt = ({ contentLabel={intl.formatMessage(messages.confirmDeletionHeading)} onRequestClose={onCancel} > - - { modalPosition === 'right' ? + + {((modalPosition === 'right' && !isRtl) || (modalPosition === 'left' && isRtl)) ? : null } @@ -160,11 +164,11 @@ const DeleteConfirmationPrompt = ({ - {modalPosition === 'left' ? + {((modalPosition === 'left' && !isRtl) || (modalPosition === 'right' && isRtl)) ? : null } @@ -177,7 +181,8 @@ DeleteConfirmationPrompt.propTypes = { relativeElemRef: PropTypes.object, entityType: PropTypes.string, modalPosition: PropTypes.string, - intl: intlShape.isRequired + intl: intlShape.isRequired, + isRtl: PropTypes.bool }; const DeleteConfirmationPromptIntl = injectIntl(DeleteConfirmationPrompt); diff --git a/packages/scratch-gui/src/components/sprite-selector/sprite-list.jsx b/packages/scratch-gui/src/components/sprite-selector/sprite-list.jsx index c496bd7325..e7888d3b3d 100644 --- a/packages/scratch-gui/src/components/sprite-selector/sprite-list.jsx +++ b/packages/scratch-gui/src/components/sprite-selector/sprite-list.jsx @@ -30,7 +30,8 @@ const SpriteList = function (props) { ordering, raised, selectedId, - items + items, + isRtl } = props; const isSpriteDrag = draggingType === DragConstants.SPRITE; @@ -95,7 +96,7 @@ const SpriteList = function (props) { onDuplicateButtonClick={onDuplicateSprite} onExportButtonClick={onExportSprite} withDeleteConfirmation - deleteConfirmationModalPosition={'left'} + deleteConfirmationModalPosition={isRtl ? 'right' : 'left'} /> ); @@ -134,7 +135,8 @@ SpriteList.propTypes = { onSelectSprite: PropTypes.func, ordering: PropTypes.arrayOf(PropTypes.number), raised: PropTypes.bool, - selectedId: PropTypes.string + selectedId: PropTypes.string, + isRtl: PropTypes.bool }; export default SortableHOC(SpriteList); diff --git a/packages/scratch-gui/src/components/sprite-selector/sprite-selector.jsx b/packages/scratch-gui/src/components/sprite-selector/sprite-selector.jsx index adc537ba46..5218a4d24f 100644 --- a/packages/scratch-gui/src/components/sprite-selector/sprite-selector.jsx +++ b/packages/scratch-gui/src/components/sprite-selector/sprite-selector.jsx @@ -111,6 +111,7 @@ const SpriteSelectorComponent = function (props) { onDuplicateSprite={onDuplicateSprite} onExportSprite={onExportSprite} onSelectSprite={onSelectSprite} + isRtl={isRtl(intl.locale)} /> ( + +); + +DeleteConfirmationPrompt.propTypes = { + isRtl: PropTypes.bool, + onCancel: PropTypes.func.isRequired, + onOk: PropTypes.func.isRequired, + modalPosition: PropTypes.string, + entityType: PropTypes.string.isRequired, + relativeElemRef: PropTypes.object.isRequired +}; + +const mapStateToProps = state => ({ + isRtl: state.locales.isRtl +}); + +export default connect( + mapStateToProps +)(DeleteConfirmationPrompt); diff --git a/packages/scratch-gui/src/containers/sprite-selector-item.jsx b/packages/scratch-gui/src/containers/sprite-selector-item.jsx index 9a2c7de8c4..5aa6edb18f 100644 --- a/packages/scratch-gui/src/containers/sprite-selector-item.jsx +++ b/packages/scratch-gui/src/containers/sprite-selector-item.jsx @@ -10,7 +10,7 @@ import getCostumeUrl from '../lib/get-costume-url'; import DragRecognizer from '../lib/drag-recognizer'; import {getEventXY} from '../lib/touch-utils'; import {GUIStoragePropType} from '../gui-config'; -import DeleteConfirmationPrompt from '../components/delete-confirmation-prompt/delete-confirmation-prompt.jsx'; +import DeleteConfirmationPrompt from './delete-confirmation-prompt'; import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx'; @@ -155,6 +155,7 @@ class SpriteSelectorItem extends React.PureComponent { relativeElemRef={this.ref} entityType={this.props.dragType} modalPosition={deleteConfirmationModalPosition} + isRtl={this.props.isRtl} /> : null} ({ @@ -202,7 +204,8 @@ const mapStateToProps = (state, {id}) => ({ dragging: state.scratchGui.assetDrag.dragging, receivedBlocks: state.scratchGui.hoveredTarget.receivedBlocks && state.scratchGui.hoveredTarget.sprite === id, - vm: state.scratchGui.vm + vm: state.scratchGui.vm, + isRtl: state.locales.isRtl }); const mapDispatchToProps = dispatch => ({ dispatchSetHoveredSprite: spriteId => { diff --git a/packages/scratch-gui/test/unit/containers/sprite-selector-item.test.jsx b/packages/scratch-gui/test/unit/containers/sprite-selector-item.test.jsx index 01e6791e94..dae799f7d4 100644 --- a/packages/scratch-gui/test/unit/containers/sprite-selector-item.test.jsx +++ b/packages/scratch-gui/test/unit/containers/sprite-selector-item.test.jsx @@ -7,7 +7,7 @@ import VM from '@scratch/scratch-vm'; import SpriteSelectorItemContainer from '../../../src/containers/sprite-selector-item'; import DeleteButton from '../../../src/components/delete-button/delete-button'; import {legacyConfig} from '../../../src/legacy-config'; -import DeleteConfirmationPrompt from '../../../src/components/delete-confirmation-prompt/delete-confirmation-prompt.jsx'; +import DeleteConfirmationPrompt from '../../../src/containers/delete-confirmation-prompt'; jest.mock('../../../src/components/delete-confirmation-prompt/delete-confirmation-prompt.jsx', () => jest.fn(() => null)); describe('SpriteSelectorItem Container', () => { From 3db9992bedaf8af693d211fd6ee9924d8cac551b Mon Sep 17 00:00:00 2001 From: pishi-ai Date: Thu, 4 Sep 2025 18:53:31 +0330 Subject: [PATCH 2/2] fix(gui): add missing locales state to sprite-selector-item test fix: resolve undefined locales.isRtl error in sprite-selector-item tests Add missing locales state to test mock store and fix DeleteConfirmationPrompt mock path. The component's mapStateToProps was trying to access state.locales.isRtl which was undefined. --- .../containers/sprite-selector-item.test.jsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/scratch-gui/test/unit/containers/sprite-selector-item.test.jsx b/packages/scratch-gui/test/unit/containers/sprite-selector-item.test.jsx index dae799f7d4..0cd70e15a2 100644 --- a/packages/scratch-gui/test/unit/containers/sprite-selector-item.test.jsx +++ b/packages/scratch-gui/test/unit/containers/sprite-selector-item.test.jsx @@ -9,7 +9,7 @@ import DeleteButton from '../../../src/components/delete-button/delete-button'; import {legacyConfig} from '../../../src/legacy-config'; import DeleteConfirmationPrompt from '../../../src/containers/delete-confirmation-prompt'; -jest.mock('../../../src/components/delete-confirmation-prompt/delete-confirmation-prompt.jsx', () => jest.fn(() => null)); +jest.mock('../../../src/containers/delete-confirmation-prompt', () => jest.fn(() => null)); describe('SpriteSelectorItem Container', () => { const mockStore = configureStore(); let className; @@ -52,12 +52,18 @@ describe('SpriteSelectorItem Container', () => { dispatchSetHoveredSprite = jest.fn(); selected = true; vm = new VM(); - store = mockStore({scratchGui: { - config: legacyConfig, - hoveredTarget: {receivedBlocks: false, sprite: null}, - assetDrag: {dragging: false}, - vm - }}); + store = mockStore({ + scratchGui: { + config: legacyConfig, + hoveredTarget: {receivedBlocks: false, sprite: null}, + assetDrag: {dragging: false}, + vm + }, + locales: { + isRtl: false, + locale: 'en-US' + } + }); }); test('should delete the sprite, when called without `withDeleteConfirmation`', () => {