Select Categories
When I was working on a library, I thought it would be really handy be able to select a whole category in the export dialog, so I decided to add it myself.
This userscript adds category names with a checkbox next to them in the export blocks dialog. When you click the checkbox, it will deselect the blocks in that category. If it doesn't do anything, then that means the blocks are used in other blocks that are selected (only in v8.0+).
This userscript actually also adds it to the unused blocks dialog.
Install
To use the userscript, you need a userscript browser extension, such as Tampermonkey. Then you can just click the link below to install the userscript.
https://ego-lay-atman-bay.github.io/snap-extensions/userscripts/select-categories.user.js
If you can't install browser extensions, then here's the source. You can run it in a javascript block, or create a bookmarklet with it.
Source
// ==UserScript==
// @name Select Categories
// @namespace https://ego-lay-atman-bay.github.io/snap-extensions
// @version 0.3
// @description Adds the ability to select and deselect categories in the export and unused blocks dialogs.
// @author ego-lay-atman-bay
// @match https://snap.berkeley.edu/*/snap.html
// @icon https://forum.snap.berkeley.edu/favicon/proxied?https%3A%2F%2Fd1eo0ig0pi5tcs.cloudfront.net%2Foptimized%2F2X%2Ff%2Ffec08d3829a26a75ae620be49835ef91b13ba8e9_2_32x32.png
// @grant none
// ==/UserScript==
(function () {
'use strict';
function checkVersion() {
if (typeof SnapVersion !== 'undefined') {
let version = SnapVersion.split('.');
return [version[0], version[1]].join('.');
}
else {
return false;
};
};
if (checkVersion() >= 7 || true) {
function injectSelectCategories(func) {
let split = func.split('lastCat,');
split.splice(1, 0, 'lastCat, catCheckBox');
func = split.join('');
let start = func.indexOf('if (lastCat && (category !== lastCat))');
if (start == -1) {
return false;
};
let end = func.indexOf('}', start);
let injectStr = "if (category !== lastCat) {\n y += padding;\n catCheckBox = new ToggleMorph(\n 'checkbox',\n this,\n () => {\n var blocks = [];\n if (contains(this.blocks.map(b => b.category), category)) {\n this.blocks.forEach(block => {\n if (block.category != category) {\n blocks.push(block)\n };\n });\n this.blocks = blocks;\n }\n else {\n this.body.contents.children.forEach(block => {\n if (block instanceof ToggleMorph) {\n if (block.element instanceof CustomReporterBlockMorph || block.element instanceof CustomCommandBlockMorph) {\n if (block.element.category == category) {\n if (!contains(this.blocks, block.element.definition)) {\n block.trigger()\n };\n };\n };\n };\n });\n };\n try {\n this.collectDependencies(); // v8.0+\n }\n catch (err) { // v7\n this.body.contents.children.forEach(checkBox => {\n if (checkBox instanceof ToggleMorph) {\n checkBox.refresh();\n }\n });\n }\n\n },\n // category,\n null,\n () => contains(this.blocks.map(b => b.category), category),\n null,\n null \n )\n // catCheckBox.label.color = new Color(255, 255, 255, 1)\n // catCheckBox.label.fontSize = 12\n // catCheckBox.label.setWidth()\n // catCheckBox.label.setTop()\n // catCheckBox.element.setTop(-(catCheckBox.fullBounds().height()/2))\n catCheckBox.setPosition(new Point(\n x,\n y + (catCheckBox.top())\n ));\n palette.addContents(catCheckBox);\n txt = SpriteMorph.prototype.categoryText(category);\n txt.setPosition(new Point(x + catCheckBox.fullBounds().width() + padding, y));\n txt.refresh = function() {};\n palette.addContents(txt);\n y += catCheckBox.fullBounds().height()\n y += padding\n }";
var newFunc = [func.substring(0, start), injectStr, func.substring(end + 1, func.length)].join('');
return newFunc;
};
console.log('adding selecting categories');
let exportDialog = BlockExportDialogMorph.prototype.buildContents.toString();
let unusedDialog = BlockRemovalDialogMorph.prototype.buildContents.toString();
let newExportDialog = injectSelectCategories(exportDialog);
let newUnusedDialog = injectSelectCategories(unusedDialog);
if (newExportDialog) {
const injectExport = new Function('BlockExportDialogMorph.prototype.buildContents = ' + newExportDialog);
injectExport();
};
if (newUnusedDialog) {
const injectUnused = new Function('BlockRemovalDialogMorph.prototype.buildContents = ' + newUnusedDialog);
injectUnused();
};
};
})();
For use in mod
Anything surrounded by // =================
is the added stuff.
src/byob.js
line 4438
if it's not there, search for BlockExportDialogMorph.prototype.buildContents = function
in src/boyob.js
.
BlockExportDialogMorph.prototype.buildContents = function () {
var palette, x, y, block, checkBox, lastCat,
padding = 4;
// =================
var catCheckBox
// =================
// create plaette
palette = new ScrollFrameMorph(
null,
null,
SpriteMorph.prototype.sliderColor
);
palette.color = SpriteMorph.prototype.paletteColor;
palette.padding = padding;
palette.isDraggable = false;
palette.acceptsDrops = false;
palette.contents.acceptsDrops = false;
palette.fontSize = InputFieldMorph.prototype.fontSize;
palette.contrast = InputFieldMorph.prototype.contrast;
// populate palette
x = palette.left() + padding;
y = palette.top() + padding;
SpriteMorph.prototype.allCategories().forEach(category => {
this.blocks.forEach(definition => {
if (definition.category === category) {
// =================
if (category !== lastCat) {
y += padding;
catCheckBox = new ToggleMorph(
'checkbox',
this,
() => {
var blocks = [];
if (contains(this.blocks.map(b => b.category), category)) {
this.blocks.forEach(block => {
if (block.category != category) {
blocks.push(block)
};
});
this.blocks = blocks;
}
else {
this.body.contents.children.forEach(block => {
if (block instanceof ToggleMorph) {
if (block.element instanceof CustomReporterBlockMorph || block.element instanceof CustomCommandBlockMorph) {
if (block.element.category == category) {
if (!contains(this.blocks, block.element.definition)) {
block.trigger()
};
};
};
};
});
};
try {
this.collectDependencies(); // v8.0+
}
catch (err) { // v7
this.body.contents.children.forEach(checkBox => {
if (checkBox instanceof ToggleMorph) {
checkBox.refresh();
}
});
}
},
// category,
null,
() => contains(this.blocks.map(b => b.category), category),
null,
null
)
// catCheckBox.label.color = new Color(255, 255, 255, 1)
// catCheckBox.label.fontSize = 12
// catCheckBox.label.setWidth()
// catCheckBox.label.setTop()
// catCheckBox.element.setTop(-(catCheckBox.fullBounds().height()/2))
catCheckBox.setPosition(new Point(
x,
y + (catCheckBox.top())
));
palette.addContents(catCheckBox);
txt = SpriteMorph.prototype.categoryText(category);
txt.setPosition(new Point(x + catCheckBox.fullBounds().width() + padding, y));
txt.refresh = function() {}; // to avoid an error when refreshing checkboxes
palette.addContents(txt);
y += catCheckBox.fullBounds().height()
y += padding
}
// =================
lastCat = category;
block = definition.templateInstance();
block.isToggleLabel = true; // mark as unrefreshable label
checkBox = new ToggleMorph(
'checkbox',
this,
() => {
var idx = this.blocks.indexOf(definition);
if (idx > -1) {
this.blocks.splice(idx, 1);
} else {
this.blocks.push(definition);
}
this.collectDependencies();
},
null,
() => contains(this.blocks, definition),
null,
null,
this.target ? block : block.fullImage()
);
checkBox.setPosition(new Point(
x,
y + (checkBox.top() - checkBox.toggleElement.top())
));
palette.addContents(checkBox);
y += checkBox.fullBounds().height() + padding;
}
});
});
palette.scrollX(padding);
palette.scrollY(padding);
this.addBody(palette);
this.addButton('ok', 'OK');
this.addButton('cancel', 'Cancel');
this.setExtent(new Point(220, 300));
this.fixLayout();
};
If you're modding v8.0, you can just replace any of the try {} catch(err) {}
with this.collectDependencies();
. The catch (err) {}
part is for v7.0.
If you're modding v8.1+, you'll also need to modify BlockRemovalDialogMorph.prototype.buildContents
(unused dialog). Where and what to add is the same.
Compatability
This works on all versions 7 and up. If you're on 6.9 or below, it will not run.
Screenshots
If you find a bug, please let me know (here or on github).
And yes, this is a feature request in disguise.