Skip to content

Commit 7a62541

Browse files
Merge branch 'develop' into windows-compat
2 parents 7d96bb3 + 09161fb commit 7a62541

File tree

13 files changed

+3515
-4559
lines changed

13 files changed

+3515
-4559
lines changed

package-lock.json

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

src/addons/addons/editor-animations/userscript.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ export default async function({ addon }) {
216216
const ogRemoveChild = document.body.constructor.prototype.removeChild;
217217
document.body.constructor.prototype.removeChild = function(child) {
218218
const element = document.querySelector(`div[class="ReactModalPortal"]`);
219-
if (!element) return;
219+
if (!element) return ogRemoveChild.call(this, child);
220220

221221
let animTime = 200;
222222
patchedBody = true;

src/addons/addons/multi-tab-code/_manifest_entry.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const manifest = {
33
"editorOnly": true,
44
"name": "Code Editor Tabbing",
5-
"description": "Adds tabs to the code editor, allowing you to devide many scripts from inside one sprite in to seperate parts.",
5+
"description": "Adds tabs to the code editor, allowing you to divide many scripts from inside one sprite in to seperate parts.",
66
"info": [],
77
"credits": [
88
{
@@ -26,8 +26,15 @@ const manifest = {
2626
"type": "boolean",
2727
"id": "shouldDelete",
2828
"name": "Merge tabs on delete",
29-
"default": "true",
29+
"default": true,
3030
"description": "If we should keep blocks from deleted tabs, moving them into the new selected tab, or if we should delete blocks inside a deleted tab."
31+
},
32+
{
33+
"type": "boolean",
34+
"id": "moveOnDrag",
35+
"name": "Move blocks into tabs",
36+
"default": false,
37+
"description": "If, when blocks are dragged over a tab, we should copy blocks into the tab or move the selected blocks into the tab."
3138
}
3239
],
3340
"tags": [],

src/addons/addons/multi-tab-code/userscript.js

Lines changed: 148 additions & 109 deletions
Large diffs are not rendered by default.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* generated by pull.js */
2+
const manifest = {
3+
"name": "Draggable Categories in Block Palette",
4+
"description": "Allows you to click-and-hold categories in the block palette to re-arrange them.",
5+
"credits": [
6+
{
7+
"name": "SharkPool",
8+
"link": "https://github.com/SharkPool-SP/"
9+
}
10+
],
11+
"info": [
12+
{
13+
"type": "notice",
14+
"text": "This addon is currently somewhat buggy when enabled alongside the \"Two-column category menu\" addon.",
15+
"id": "draggable-incompatibility"
16+
}
17+
],
18+
"userscripts": [
19+
{
20+
"url": "userscript.js"
21+
}
22+
],
23+
"userstyles": [
24+
{
25+
"url": "userstyle.css"
26+
}
27+
],
28+
"tags": ["editor", "new", "recommended"],
29+
"enabledByDefault": false,
30+
"dynamicEnable": true,
31+
"dynamicDisable": false
32+
};
33+
export default manifest;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* generated by pull.js */
2+
import _js from "./userscript.js";
3+
import _css from "!css-loader!./userstyle.css";
4+
export const resources = {
5+
"userscript.js": _js,
6+
"userstyle.css": _css,
7+
};
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// Toolbox Category Drag
2+
// By: SharkPool
3+
export default async function ({ addon }) {
4+
// wait for scratchblocks to be defined
5+
await addon.tab.traps.getBlockly();
6+
7+
const COMMENT_TRAPPER_ID = "--Category_Order_ADDON-config";
8+
const soup = "!#%()*+,-./:;=?@[]^_`{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
9+
10+
let categoryOrdering = undefined;
11+
12+
const genUID = () => {
13+
const id = [];
14+
for (let i = 0; i < 20; i++) {
15+
id[i] = soup.charAt(Math.random() * soup.length);
16+
}
17+
return id.join("");
18+
}
19+
20+
const createSep = () => {
21+
const sep = document.createElement("sep");
22+
sep.setAttribute("gap", "36");
23+
return sep;
24+
};
25+
26+
const extractCategoryID = (classList) => {
27+
for (const text of classList) {
28+
if (text.startsWith("scratchCategoryId-")) return text.replace("scratchCategoryId-", "");
29+
}
30+
return undefined;
31+
}
32+
33+
const ogPopulate = ScratchBlocks.Toolbox.CategoryMenu.prototype.populate;
34+
ScratchBlocks.Toolbox.CategoryMenu.prototype.populate = function (...args) {
35+
if (!categoryOrdering) {
36+
ogPopulate.call(this, ...args);
37+
return;
38+
}
39+
40+
const toolboxXml = args[0];
41+
const children = Array.from(toolboxXml.children);
42+
const categories = children.filter(e => e.tagName === "category");
43+
44+
/* sort categories based on categoryOrdering */
45+
categories.sort((a, b) => {
46+
const aIndex = categoryOrdering.indexOf(a.getAttribute("id"));
47+
const bIndex = categoryOrdering.indexOf(b.getAttribute("id"));
48+
return (aIndex === -1 ? 999 : aIndex) - (bIndex === -1 ? 999 : bIndex);
49+
});
50+
51+
while (toolboxXml.firstChild) toolboxXml.removeChild(toolboxXml.firstChild);
52+
53+
/* <sep> + <category> + <sep> + ... + <category> + <sep> */
54+
toolboxXml.appendChild(createSep());
55+
categories.forEach((cat) => {
56+
toolboxXml.appendChild(cat);
57+
toolboxXml.appendChild(createSep());
58+
});
59+
60+
ogPopulate.call(this, ...args);
61+
};
62+
63+
const ogSaveJSON = vm.toJSON;
64+
vm.toJSON = function (...args) {
65+
saveOrdering();
66+
return ogSaveJSON.call(this, ...args);
67+
}
68+
69+
vm.runtime.on("PROJECT_LOADED", () => {
70+
const storedOrder = findOrderingComment(true);
71+
if (storedOrder) {
72+
try {
73+
categoryOrdering = JSON.parse(storedOrder);
74+
setTimeout(forceRefreshToolbox, 100);
75+
} catch { }
76+
}
77+
});
78+
79+
function findOrderingComment(optParse) {
80+
const stageTarget = vm.runtime.getTargetForStage();
81+
if (!stageTarget) return undefined;
82+
83+
let configComment;
84+
const comments = Object.values(stageTarget.comments);
85+
for (const comment of comments) {
86+
if (comment.text.endsWith(COMMENT_TRAPPER_ID)) {
87+
configComment = comment.text;
88+
break;
89+
}
90+
}
91+
92+
if (optParse && configComment) {
93+
const dataLine = configComment.split("\n").find(i => i.endsWith(COMMENT_TRAPPER_ID));
94+
if (!dataLine) return undefined;
95+
return dataLine.substr(0, dataLine.length - COMMENT_TRAPPER_ID.length);
96+
}
97+
}
98+
99+
function saveOrdering() {
100+
if (findOrderingComment()) return;
101+
102+
const stageTarget = vm.runtime.getTargetForStage();
103+
if (!stageTarget) return;
104+
105+
const text = `Configuration for 'Category Ordering' Addon\nYou can move, resize, and minimize this comment, but don't edit it by hand. This comment can be deleted to remove the stored settings.\n${JSON.stringify(categoryOrdering)}${COMMENT_TRAPPER_ID}`;
106+
stageTarget.createComment(genUID(), null, text, 50, 50, 350, 170, false);
107+
vm.runtime.emitProjectChanged();
108+
}
109+
110+
function compileNewOrder(htmlCategoryList) {
111+
const orderedIDs = [];
112+
for (const cat of htmlCategoryList) {
113+
const id = extractCategoryID(cat.firstChild.classList);
114+
if (id) orderedIDs.push(id);
115+
}
116+
categoryOrdering = orderedIDs;
117+
}
118+
119+
function forceRefreshToolbox() {
120+
const workspace = ScratchBlocks.getMainWorkspace();
121+
const toolbox = workspace.getToolbox();
122+
if (!toolbox) return;
123+
const categoryMenu = toolbox.categoryMenu_;
124+
if (!categoryMenu) return;
125+
if (categoryMenu.secondTable) return;
126+
127+
categoryMenu.dispose();
128+
categoryMenu.createDom();
129+
toolbox.populate_(workspace.options.languageTree);
130+
toolbox.position();
131+
}
132+
133+
function initDragDroper(clickEvent) {
134+
const draggedCat = clickEvent.target.closest(`div[class="scratchCategoryMenuRow"]`);
135+
if (!draggedCat) return;
136+
137+
const categoryList = blocklyToolboxDiv.querySelectorAll(`div[class*="scratchCategoryMenuRow"]`);
138+
139+
const rect = draggedCat.getBoundingClientRect();
140+
const generalHeight = rect.height;
141+
const offsetX = clickEvent.clientX - rect.left;
142+
const offsetY = clickEvent.clientY - rect.top;
143+
144+
const dragger = draggedCat.cloneNode(true);
145+
draggedCat.style.opacity = 0.5;
146+
147+
dragger.setAttribute("style", `position: absolute; z-index: 99999; left: ${rect.left}px; top: ${rect.top}px; width: ${rect.width}px; pointer-events: none;`);
148+
dragger.firstChild.setAttribute("style", `box-shadow: #000 5px 5px 10px; border-radius: 8px;`);
149+
dragger.dataset.dragger = true;
150+
document.body.appendChild(dragger);
151+
152+
let lastHovered = null;
153+
154+
const onMouseMove = (moveEvent) => {
155+
/* drag visual */
156+
const newLeft = moveEvent.clientX - offsetX;
157+
const newTop = moveEvent.clientY - offsetY;
158+
dragger.style.left = `${newLeft}px`;
159+
dragger.style.top = `${newTop}px`;
160+
161+
// auto scroll if dragger is near the top/bottom
162+
const scrollZoneSize = 40;
163+
const bounds = blocklyToolboxDiv.getBoundingClientRect();
164+
165+
if (moveEvent.clientY < bounds.top + scrollZoneSize) {
166+
blocklyToolboxDiv.scrollTop -= 4;
167+
} else if (moveEvent.clientY > bounds.bottom - scrollZoneSize) {
168+
blocklyToolboxDiv.scrollTop += 4;
169+
}
170+
171+
// check if we are near any category
172+
// if so, bump down everything below the dragger
173+
let target;
174+
for (const cat of categoryList) {
175+
if (cat === draggedCat) continue;
176+
const catRect = cat.getBoundingClientRect();
177+
const midpointY = catRect.top + catRect.height / 2;
178+
const midpointX = catRect.left + catRect.width / 2;
179+
180+
const xDist = Math.abs(moveEvent.clientX - midpointX);
181+
const yCheck = moveEvent.clientY < midpointY;
182+
if (yCheck && xDist < 100) {
183+
target = cat;
184+
break;
185+
}
186+
}
187+
188+
for (const cat of categoryList) cat.style.transform = "";
189+
if (target) {
190+
lastHovered = target;
191+
let shifter = target;
192+
while (shifter) {
193+
if (shifter === draggedCat) return;
194+
shifter.style.transform = `translateY(${generalHeight}px)`;
195+
shifter = shifter.nextSibling;
196+
}
197+
} else {
198+
lastHovered = null;
199+
}
200+
};
201+
const onMouseUp = () => {
202+
/* cleanup */
203+
document.removeEventListener("mousemove", onMouseMove);
204+
document.removeEventListener("mouseup", onMouseUp);
205+
for (const cat of categoryList) cat.style.transform = "";
206+
draggedCat.style.opacity = "";
207+
dragger.remove();
208+
209+
// if the category drag was valid, move the category
210+
if (lastHovered) {
211+
const id = extractCategoryID(draggedCat.firstChild.classList);
212+
draggedCat.parentNode.insertBefore(draggedCat, lastHovered);
213+
214+
const newCatList = blocklyToolboxDiv.querySelectorAll(`div[class*="scratchCategoryMenuRow"]`);
215+
compileNewOrder(newCatList);
216+
setTimeout(() => {
217+
forceRefreshToolbox();
218+
if (id) ScratchBlocks.mainWorkspace.toolbox_.setSelectedCategoryById(id);
219+
}, 100);
220+
}
221+
};
222+
223+
document.addEventListener("mousemove", onMouseMove);
224+
document.addEventListener("mouseup", onMouseUp);
225+
}
226+
227+
/* Check for Long (500ms) Presses to not confuse with Selecting Categories */
228+
const blocklyToolboxDiv = document.querySelector(`div[class*="blocklyToolboxDiv"`);
229+
blocklyToolboxDiv.addEventListener("mousedown", (e) => {
230+
const longPressTimer = setTimeout(() => initDragDroper(e), 500);
231+
const cancel = () => clearTimeout(longPressTimer);
232+
233+
document.addEventListener("mouseup", cancel, { once: true });
234+
document.addEventListener("mouseleave", cancel, { once: true });
235+
});
236+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
div[class="scratchCategoryMenuRow"][data-dragger="true"] > div {
2+
background: white;
3+
font-size: 0.65rem;
4+
}
5+
6+
[theme="dark"] div[class="scratchCategoryMenuRow"][data-dragger="true"] > div {
7+
background: var(--ui-secondary);
8+
}

src/addons/generated/addon-entries.js

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

src/addons/generated/addon-manifests.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import _disable_paste_offset from "../addons/disable-paste-offset/_manifest_entr
6161
import _block_duplicate from "../addons/block-duplicate/_manifest_entry.js";
6262
import _swap_local_global from "../addons/swap-local-global/_manifest_entry.js";
6363
import _toolbox_full_blocks_on_hover from "../addons/toolbox-full-blocks-on-hover/_manifest_entry.js";
64+
import _toolbox_category_drag from "../addons/toolbox-category-drag/_manifest_entry.js";
6465
import _editor_comment_previews from "../addons/editor-comment-previews/_manifest_entry.js";
6566
import _columns from "../addons/columns/_manifest_entry.js";
6667
import _number_pad from "../addons/number-pad/_manifest_entry.js";
@@ -136,6 +137,7 @@ export default {
136137
"block-duplicate": _block_duplicate,
137138
"swap-local-global": _swap_local_global,
138139
"toolbox-full-blocks-on-hover": _toolbox_full_blocks_on_hover,
140+
"toolbox-category-drag": _toolbox_category_drag,
139141
"editor-comment-previews": _editor_comment_previews,
140142
"columns": _columns,
141143
"number-pad": _number_pad,

0 commit comments

Comments
 (0)