Skip to content

Commit 924dde2

Browse files
committed
fix custom/sound right click menu
1 parent 60f6b10 commit 924dde2

11 files changed

Lines changed: 110 additions & 34 deletions

File tree

src/addons/addons/folders/userscript.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,21 @@ export default async function ({ addon, console, msg }) {
138138
return null;
139139
};
140140

141+
const getSpriteSelectorComponent = (element) => {
142+
const candidates = [element, element && element.querySelector && element.querySelector(".react-contextmenu-wrapper")];
143+
for (const candidate of candidates) {
144+
let fiber = candidate && candidate[addon.tab.traps.getInternalKey(candidate)];
145+
while (fiber) {
146+
const component = fiber.stateNode;
147+
if (component && component.props && component.props.dragType) {
148+
return component;
149+
}
150+
fiber = fiber.return;
151+
}
152+
}
153+
return null;
154+
};
155+
141156
const openFolderAsset = {
142157
assetId: "&__sa_folders_folder",
143158
encodeDataURI() {
@@ -747,7 +762,8 @@ export default async function ({ addon, console, msg }) {
747762
await addon.tab.scratchClassReady();
748763
addon.tab.createEditorContextMenu((ctxType, ctx) => {
749764
if (ctxType !== "sprite" && ctxType !== "costume" && ctxType !== "sound") return;
750-
const component = ctx.target[addon.tab.traps.getInternalKey(ctx.target)].return.return.return.stateNode;
765+
const component = getSpriteSelectorComponent(ctx.target);
766+
if (!component) return;
751767
const data = getItemData(component.props);
752768
if (!data) return;
753769
if (typeof data.folder === "string") {
@@ -872,6 +888,11 @@ export default async function ({ addon, console, msg }) {
872888
});
873889

874890
const patchSpriteSelectorItem = (SpriteSelectorItem) => {
891+
if (SpriteSelectorItem.prototype.saFoldersPatched) {
892+
return;
893+
}
894+
SpriteSelectorItem.prototype.saFoldersPatched = true;
895+
875896
for (const method of ["handleDelete", "handleDuplicate", "handleExport"]) {
876897
const original = SpriteSelectorItem.prototype[method];
877898
SpriteSelectorItem.prototype[method] = function (...args) {
@@ -1351,7 +1372,11 @@ export default async function ({ addon, console, msg }) {
13511372
reduxCondition: (state) => state.scratchGui.editorTab.activeTabIndex !== 0 && !state.scratchGui.mode.isPlayerOnly,
13521373
});
13531374
const sortableHOCInstance = getSortableHOCFromElement(selectorListItem);
1375+
const spriteSelectorItemInstance = getSpriteSelectorComponent(selectorListItem);
13541376
verifySortableHOC(sortableHOCInstance);
1377+
if (spriteSelectorItemInstance) {
1378+
patchSpriteSelectorItem(spriteSelectorItemInstance.constructor);
1379+
}
13551380
patchSortableHOC(sortableHOCInstance.constructor, TYPE_ASSETS);
13561381
sortableHOCInstance.saInitialSetup();
13571382
}

src/addons/contextmenu.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ const findBodyContextMenuById = (tab, menuId) => {
3030
) || null;
3131
};
3232

33+
const getSpriteSelectorProps = (tab, element) => {
34+
const candidates = [element, element && element.querySelector && element.querySelector(".react-contextmenu-wrapper")];
35+
for (const candidate of candidates) {
36+
let fiber = candidate && candidate[tab.traps.getInternalKey(candidate)];
37+
while (fiber) {
38+
const props = fiber.stateNode && fiber.stateNode.props;
39+
if (props && props.dragType) return props;
40+
fiber = fiber.return;
41+
}
42+
}
43+
return null;
44+
};
45+
3346
const onReactContextMenu = function (e) {
3447
if (!e.target) return;
3548
const ctxTarget = e.target.closest(".react-contextmenu-wrapper");
@@ -55,9 +68,9 @@ const onReactContextMenu = function (e) {
5568
extra.itemId = props.id;
5669
extra.targetId = props.targetId;
5770
type = `monitor_${props.mode}`;
58-
} else if (ctxTarget[this.traps.getInternalKey(ctxTarget)]?.return?.return?.return?.stateNode?.props?.dragType) {
71+
} else if (getSpriteSelectorProps(this, ctxTarget)) {
5972
// SpriteSelectorItem which despite its name is used for costumes, sounds, backpacked script etc
60-
const props = ctxTarget[this.traps.getInternalKey(ctxTarget)].return.return.return.stateNode.props;
73+
const props = getSpriteSelectorProps(this, ctxTarget);
6174
type = props.dragType.toLowerCase();
6275
extra.name = props.name;
6376
extra.itemId = props.id;
@@ -98,7 +111,9 @@ const onReactContextMenu = function (e) {
98111
display: "block",
99112
});
100113

101-
itemElem.addEventListener("click", (e) => {
114+
const activateItem = (e) => {
115+
if (e.button !== 0) return;
116+
e.preventDefault();
102117
e.stopPropagation();
103118
window.dispatchEvent(
104119
new CustomEvent("REACT_CONTEXTMENU_HIDE", {
@@ -108,6 +123,14 @@ const onReactContextMenu = function (e) {
108123
})
109124
);
110125
item.callback(ctx);
126+
};
127+
128+
itemElem.addEventListener("mousedown", activateItem);
129+
itemElem.addEventListener("click", (e) => {
130+
if (e.detail !== 0) {
131+
e.preventDefault();
132+
e.stopPropagation();
133+
}
111134
});
112135

113136
this.appendToSharedSpace({

src/components/box/box.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ Box.propTypes = {
100100
* A callback function whose first parameter is the underlying dom elements.
101101
* This call back will be executed immediately after the component is mounted or unmounted
102102
*/
103-
componentRef: PropTypes.func,
103+
componentRef: PropTypes.oneOfType([
104+
PropTypes.func,
105+
PropTypes.shape({current: PropTypes.any})
106+
]),
104107
/** https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction */
105108
direction: PropTypes.oneOf([
106109
'row', 'row-reverse', 'column', 'column-reverse'

src/components/context-menu/context-menu.jsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,30 @@ const StyledContextMenu = props => (
1414
const StyledMenuItem = props => (
1515
<MenuItem
1616
{...props}
17-
attributes={{className: styles.menuItem}}
17+
attributes={{
18+
className: styles.menuItem,
19+
onMouseDown: props.onMouseDown
20+
}}
1821
/>
1922
);
2023

2124
const BorderedMenuItem = props => (
2225
<MenuItem
2326
{...props}
24-
attributes={{className: classNames(styles.menuItem, styles.menuItemBordered)}}
27+
attributes={{
28+
className: classNames(styles.menuItem, styles.menuItemBordered),
29+
onMouseDown: props.onMouseDown
30+
}}
2531
/>
2632
);
2733

2834
const DangerousMenuItem = props => (
2935
<MenuItem
3036
{...props}
31-
attributes={{className: classNames(styles.menuItem, styles.menuItemBordered, styles.menuItemDanger)}}
37+
attributes={{
38+
className: classNames(styles.menuItem, styles.menuItemBordered, styles.menuItemDanger),
39+
onMouseDown: props.onMouseDown
40+
}}
3241
/>
3342
);
3443

src/components/sprite-selector-item/sprite-selector-item.jsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import ReactDOM from 'react-dom';
55

66
import DeleteButton from '../delete-button/delete-button.jsx';
77
import styles from './sprite-selector-item.css';
8-
import {ContextMenuTrigger} from 'react-contextmenu';
8+
import {ContextMenuTrigger, hideMenu} from 'react-contextmenu';
99
import {DangerousMenuItem, ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
1010
import {FormattedMessage} from 'react-intl';
1111

@@ -19,10 +19,23 @@ const SpriteSelectorItem = props => {
1919
}
2020
const menuId = contextMenuIdRef.current;
2121

22+
const handleMenuMouseDown = onClick => event => {
23+
if (event.button !== 0) {
24+
return;
25+
}
26+
event.preventDefault();
27+
event.stopPropagation();
28+
onClick(event);
29+
hideMenu();
30+
};
31+
2232
const menu = props.onDuplicateButtonClick || props.onDeleteButtonClick || props.onExportButtonClick ? (
2333
<ContextMenu id={menuId}>
2434
{props.onDuplicateButtonClick ? (
25-
<MenuItem onClick={props.onDuplicateButtonClick}>
35+
<MenuItem
36+
onClick={null}
37+
onMouseDown={handleMenuMouseDown(props.onDuplicateButtonClick)}
38+
>
2639
<FormattedMessage
2740
defaultMessage="duplicate"
2841
description="Menu item to duplicate in the right click menu"
@@ -31,7 +44,10 @@ const SpriteSelectorItem = props => {
3144
</MenuItem>
3245
) : null}
3346
{props.onExportButtonClick ? (
34-
<MenuItem onClick={props.onExportButtonClick}>
47+
<MenuItem
48+
onClick={null}
49+
onMouseDown={handleMenuMouseDown(props.onExportButtonClick)}
50+
>
3551
<FormattedMessage
3652
defaultMessage="export"
3753
description="Menu item to export the selected item"
@@ -40,7 +56,10 @@ const SpriteSelectorItem = props => {
4056
</MenuItem>
4157
) : null }
4258
{props.onRenameButtonClick ? (
43-
<MenuItem onClick={props.onRenameButtonClick}>
59+
<MenuItem
60+
onClick={null}
61+
onMouseDown={handleMenuMouseDown(props.onRenameButtonClick)}
62+
>
4463
<FormattedMessage
4564
defaultMessage="rename"
4665
description="Menu item to rename an item"
@@ -49,7 +68,10 @@ const SpriteSelectorItem = props => {
4968
</MenuItem>
5069
) : null}
5170
{props.onDeleteButtonClick ? (
52-
<DangerousMenuItem onClick={props.onDeleteButtonClick}>
71+
<DangerousMenuItem
72+
onClick={null}
73+
onMouseDown={handleMenuMouseDown(props.onDeleteButtonClick)}
74+
>
5375
<FormattedMessage
5476
defaultMessage="delete"
5577
description="Menu item to delete in the right click menu"
@@ -112,7 +134,7 @@ const SpriteSelectorItem = props => {
112134
/>
113135
) : null }
114136
</ContextMenuTrigger>
115-
{menu && typeof document !== 'undefined' ? ReactDOM.createPortal(menu, document.body) : menu}
137+
{menu && typeof document !== 'undefined' ? ReactDOM.createPortal(menu, document.body) : null}
116138
</React.Fragment>
117139
);
118140
};
@@ -123,6 +145,7 @@ SpriteSelectorItem.propTypes = {
123145
costumeURL: PropTypes.string,
124146
details: PropTypes.string,
125147
disableContextMenu: PropTypes.bool,
148+
dragType: PropTypes.string,
126149
// eslint-disable-next-line react/forbid-prop-types
127150
id: PropTypes.any,
128151
// eslint-disable-next-line react/forbid-prop-types

src/containers/costume-tab.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ class CostumeTab extends React.Component {
133133
this.setState({selectedCostumeIndex: costumeIndex});
134134
}
135135
handleDeleteCostume (costumeIndex) {
136-
const restoreCostumeFun = this.props.vm.deleteCostume(costumeIndex);
136+
const index = typeof costumeIndex === 'number' ? costumeIndex : this.state.selectedCostumeIndex;
137+
const restoreCostumeFun = this.props.vm.deleteCostume(index);
137138
this.props.dispatchUpdateRestore({
138139
restoreFun: restoreCostumeFun,
139140
deletedItem: 'Costume'

src/containers/sound-tab.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ class SoundTab extends React.Component {
8989
}
9090

9191
handleDeleteSound (soundIndex) {
92-
const restoreFun = this.props.vm.deleteSound(soundIndex);
93-
if (soundIndex >= this.state.selectedSoundIndex) {
94-
this.setState({selectedSoundIndex: Math.max(0, soundIndex - 1)});
92+
const index = typeof soundIndex === 'number' ? soundIndex : this.state.selectedSoundIndex;
93+
const restoreFun = this.props.vm.deleteSound(index);
94+
if (index >= this.state.selectedSoundIndex) {
95+
this.setState({selectedSoundIndex: Math.max(0, index - 1)});
9596
}
9697
this.props.dispatchUpdateRestore({restoreFun, deletedItem: 'Sound'});
9798
}

src/containers/sprite-selector-item.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ class SpriteSelectorItem extends React.PureComponent {
9191
}
9292
}
9393
handleDelete (e) {
94+
if (e && e.preventDefault) {
95+
e.preventDefault();
96+
}
9497
if (e && e.stopPropagation) {
9598
e.stopPropagation(); // To prevent from bubbling back to handleClick
9699
}

src/lib/tw-packager-integration-hoc.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ const PackagerIntegrationHOC = function (WrappedComponent) {
3737
return;
3838
}
3939

40-
const packagerData = e.data.p4;
40+
const packagerData = e.data && typeof e.data === 'object' ? e.data.p4 : null;
41+
if (!packagerData || typeof packagerData !== 'object') {
42+
return;
43+
}
4144
if (packagerData.type !== 'ready-for-import') {
4245
return;
4346
}

translations/messages/node_modules/scratch-paint/src/components/mode-tools/mode-tools.json

Lines changed: 0 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)