// ==UserScript==
// @name App Inventer 2 block helper
// @version 0.6.1
// @namespace App Inventer 2 block helper
// @description An easy way to operate blocks at MIT App Inventor 2.
// @author [email protected]
// @match *://ai2.appinventor.mit.edu/*
// @match *://code.appinventor.mit.edu/*
// @license MIT
// ==/UserScript==
(function() {
//'use strict';
setTimeout(() => {
var lastIndex = -1;
var blocks;
let container = document.querySelector(".ya-ProjectName").parentElement.nextElementSibling.firstChild.firstChild.firstChild;
let td = document.createElement("td");
td.style = "vertical-align: top;";
container.appendChild(td);
let input = document.createElement("input");
input.type = "text";
input.size = "15";
input.id = "myInput";
input.placeholder = "key word here...";
td.appendChild(input);
let td2 = document.createElement("td");
td2.style = "vertical-align: top;";
container.appendChild(td2);
let button = document.createElement("button");
button.id = "mySearch";
button.className = "ode-TextButton ode-TextButton-up";
button.innerHTML = "Search";
td2.appendChild(button);
button.addEventListener("click", () => {
if (input.value) {
findBlock(input.value);
}
});
let td3 = document.createElement("td");
td3.style = "vertical-align: top;";
container.appendChild(td3);
let clear = document.createElement("button");
clear.id = "myClear";
clear.className = "ode-TextButton ode-TextButton-up";
clear.innerHTML = "Clear";
td3.appendChild(clear);
clear.addEventListener("click", () => {
input.value = "";
if (blocks && lastIndex > -1 && lastIndex < blocks.length) {
blocks[lastIndex].setHighlighted(false);
lastIndex = -1;
}
span.innerHTML = "";
});
let td4 = document.createElement("td");
td4.style = "vertical-align: top;";
container.appendChild(td4);
let removeComment = document.createElement("button");
removeComment.id = "myRemoveComment";
removeComment.className = "ode-TextButton ode-TextButton-up";
removeComment.innerHTML = "Remove Comments";
td4.appendChild(removeComment);
removeComment.addEventListener("click", () => {
Blockly.getMainWorkspace().getAllBlocks().forEach(b=>{b.setCommentText(null)});
});
let td5 = document.createElement("td");
td5.style = "vertical-align: top;";
container.appendChild(td5);
let downloadAll = document.createElement("button");
downloadAll.id = "myDownloadAll";
downloadAll.className = "ode-TextButton ode-TextButton-up";
downloadAll.innerHTML = "Download All";
td5.appendChild(downloadAll);
downloadAll.addEventListener("click", () => {
downloadPNGIgnoreOrphan();
});
let td6 = document.createElement("td");
td6.style = "vertical-align: top;";
container.appendChild(td6);
let span = document.createElement("span");
span.id = "progressIndicator";
span.innerHTML = "";
td6.appendChild(span);
function findBlock(keyword){
blocks = Blockly.getMainWorkspace().getAllBlocks().filter(block=>simpleString(block).toLowerCase().includes(keyword.toLowerCase()));
if(lastIndex > -1){
blocks[lastIndex].setHighlighted(false);
}
lastIndex++;
if(lastIndex<blocks.length){
expand(blocks[lastIndex]);
Blockly.getMainWorkspace().cleanUp()
Blockly.getMainWorkspace().centerOnBlock(blocks[lastIndex].id);
blocks[lastIndex].select();
blocks[lastIndex].setHighlighted(true);
}else{
lastIndex = -1;
}
span.innerHTML = (lastIndex+1) + "/" + blocks.length;
}
function expand(block){
block.setCollapsed(false);
let parent = block.getParent();
if(parent){
expand(parent);
}
}
function simpleString(block) {
var text = '';
for (var i = 0, input;
(input = block.inputList[i]); i++) {
if (input.name == Blockly.BlockSvg.COLLAPSED_INPUT_NAME) {
continue;
}
for (var j = 0, field;
(field = input.fieldRow[j]); j++) {
text += field.getText() + ' ';
}
}
text = goog.string.trim(text) || '???';
return text;
}
function downloadPNGIgnoreOrphan(){
var types=["component_event","global_declaration","procedures_defreturn","procedures_defnoreturn"];
var topblocks=Blockly.getMainWorkspace().getTopBlocks();
var blocks=topblocks.filter((block)=>{
return types.indexOf(block.type)>=0
});
var i=0;
var timer=setTimeout(function(){
if(i<blocks.length){
document.querySelector("#progressIndicator").innerHTML = (i+1)+"/"+blocks.length;
exportBlockAsPng(blocks[i]);
i++;
timer=setTimeout(arguments.callee,1000)
}else{
//window.postMessage({message:"total "+i+" image(s) downloaded"},"*");
}
},1000);
}
var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
function isExternal(url) {
return url && url.lastIndexOf('http', 0) == 0 && url.lastIndexOf(window.location.host) == -1
}
function styles(el, selectorRemap) {
var css = "";
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
if (isExternal(sheets[i].href)) {
console.warn("Cannot include styles from other hosts: " + sheets[i].href);
continue
}
var rules = null;
try {
rules = sheets[i].cssRules
} catch (e) {
console.warn('Skipping a potentially injected stylesheet', e);
continue
}
if (rules != null) {
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
if (typeof(rule.style) != "undefined") {
var match = null;
try {
match = el.querySelector(rule.selectorText)
} catch (err) {
console.warn('Invalid CSS selector "' + rule.selectorText + '"', err)
}
if (match && rule.selectorText.indexOf("blocklySelected") == -1) {
var selector = selectorRemap ? selectorRemap(rule.selectorText) : rule.selectorText;
css += selector + " { " + rule.style.cssText + " }"
} else if (rule.cssText.match(/^@font-face/)) {
css += rule.cssText + ''
}
}
}
}
}
return css
}
function svgAsDataUri(el, optmetrics, options, cb) {
options = options || {};
options.scale = options.scale || 1;
var xmlns = "http://www.w3.org/2000/xmlns/";
var outer = document.createElement("div");
var textAreas = document.getElementsByTagName("textarea");
for (var i = 0; i < textAreas.length; i++) {
textAreas[i].innerHTML = textAreas[i].value
}
var clone = el.cloneNode(true);
var width, height;
if (el.tagName == 'svg') {
var box = el.getBoundingClientRect();
width = box.width || parseInt(clone.getAttribute('width') || clone.style.width || window.getComputedStyle(el).getPropertyValue('width'));
height = box.height || parseInt(clone.getAttribute('height') || clone.style.height || window.getComputedStyle(el).getPropertyValue('height'));
var left = (parseFloat(optmetrics.contentLeft) - parseFloat(optmetrics.viewLeft)).toString();
var top = (parseFloat(optmetrics.contentTop) - parseFloat(optmetrics.viewTop)).toString();
var right = (parseFloat(optmetrics.contentWidth)).toString();
var bottom = (parseFloat(optmetrics.contentHeight)).toString();
clone.setAttribute("viewBox", left + " " + top + " " + right + " " + bottom)
} else {
var matrix = el.getScreenCTM();
//clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, '').replace(/scale\(.*?\)/, '').trim());
clone.setAttribute('transform', "");
var box = el.getBBox();
width = box.width;
height = box.height;
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svg.appendChild(clone);
clone = svg;
clone.setAttribute('viewBox', box.x + " " + box.y + " " + width + " " + height)
}
clone.setAttribute("version", "1.1");
clone.setAttribute("width", width);
clone.setAttribute("height", height);
clone.setAttribute("style", 'background-color: rgba(255, 255, 255, 0);');
outer.appendChild(clone);
var css = styles(el, options.selectorRemap);
var s = document.createElement('style');
s.setAttribute('type', 'text/css');
s.innerHTML = "<![CDATA[" + css + "]]>";
var defs = document.createElement('defs');
defs.appendChild(s);
clone.insertBefore(defs, clone.firstChild);
var toHide = clone.getElementsByClassName("blocklyScrollbarHandle");
for (var i = 0; i < toHide.length; i++) {
toHide[i].setAttribute("visibility", "hidden")
}
toHide = clone.getElementsByClassName("blocklyScrollbarBackground");
for (var i = 0; i < toHide.length; i++) {
toHide[i].setAttribute("visibility", "hidden")
}
toHide = clone.querySelectorAll('image');
for (var i = 0; i < toHide.length; i++) {
toHide[i].setAttribute("visibility", "hidden")
}
toHide = clone.querySelectorAll('.blocklyMainBackground');
for (var i = 0; i < toHide.length; i++) {
toHide[i].parentElement.removeChild(toHide[i])
}
var zelement = clone.getElementById("rectCorner");
if (zelement) {
zelement.setAttribute("visibility", "hidden")
}
zelement = clone.getElementById("indicatorWarning");
if (zelement) {
zelement.setAttribute("visibility", "hidden")
}
var svg = doctype + outer.innerHTML;
svg = svg.replace(/ /g, ' ');
svg = svg.replace(/sans-serif/g, 'Arial, Verdana, "Nimbus Sans L", Helvetica');
var uri = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svg)));
if (cb) {
cb(uri)
}
}
function makeCRCTable() {
var c;
var crcTable = [];
for (var n = 0; n < 256; n++) {
c = n;
for (var k = 0; k < 8; k++) {
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1))
}
crcTable[n] = c
}
return crcTable
}
function crc32(data) {
var crcTable = window.crcTable || (window.crcTable = makeCRCTable());
var crc = 0 ^ (-1);
for (var i = 0; i < data.length; i++) {
crc = (crc >>> 8) ^ crcTable[(crc ^ data[i]) & 0xFF]
}
return (crc ^ (-1)) >>> 0
}
var CODE_PNG_CHUNK = 'coDe';
function PNG() {
this.chunks = null
}
PNG.HEADER = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
var pHY_data = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
PNG.Chunk = function(length, type, data, crc) {
this.length = length;
this.type = type;
this.data = data;
this.crc = crc
};
PNG.prototype.readFromBlob = function(blob, callback) {
var reader = new FileReader();
var png = this;
reader.addEventListener('loadend', function() {
png.processData_(new Uint8Array(reader.result));
if (callback instanceof Function) callback(png)
});
reader.readAsArrayBuffer(blob)
};
PNG.prototype.getCodeChunk = function() {
if (!this.chunks) return null;
for (var i = 0; i < this.chunks.length; i++) {
if (this.chunks[i].type === CODE_PNG_CHUNK) {
return this.chunks[i]
}
}
return null
};
PNG.prototype.processData_ = function(data) {
var chunkStart = PNG.HEADER.length;
function decode4() {
var num;
num = data[chunkStart++];
num = num * 256 + data[chunkStart++];
num = num * 256 + data[chunkStart++];
num = num * 256 + data[chunkStart++];
return num
}
function read4() {
var str = '';
for (var i = 0; i < 4; i++, chunkStart++) {
str += String.fromCharCode(data[chunkStart])
}
return str
}
function readData(length) {
return data.slice(chunkStart, chunkStart + length)
}
this.chunks = [];
while (chunkStart < data.length) {
var length = decode4();
var type = read4();
var chunkData = readData(length);
chunkStart += length;
var crc = decode4();
this.chunks.push(new PNG.Chunk(length, type, chunkData, crc))
}
};
PNG.prototype.setCodeChunk = function(code) {
var text = new TextEncoder().encode(CODE_PNG_CHUNK + code);
var length = text.length - 4;
var crc = crc32(text);
text = text.slice(4);
for (var i = 0, chunk;
(chunk = this.chunks[i]); i++) {
if (chunk.type === CODE_PNG_CHUNK) {
chunk.length = length;
chunk.data = text;
chunk.crc = crc;
return
}
}
chunk = new PNG.Chunk(length, CODE_PNG_CHUNK, text, crc);
this.chunks.splice(this.chunks.length - 1, 0, chunk)
};
PNG.prototype.toBlob = function() {
var length = PNG.HEADER.length;
this.chunks.forEach(function(chunk) {
length += chunk.length + 12
});
var buffer = new Uint8Array(length);
var index = 0;
function write4(value) {
if (typeof value === 'string') {
var text = new TextEncoder().encode(value);
buffer.set(text, index);
index += text.length
} else {
buffer[index + 3] = value & 0xFF;
value >>= 8;
buffer[index + 2] = value & 0xFF;
value >>= 8;
buffer[index + 1] = value & 0xFF;
value >>= 8;
buffer[index] = value & 0xFF;
index += 4
}
}
function writeData(data) {
buffer.set(data, index);
index += data.length
}
writeData(PNG.HEADER);
this.chunks.forEach(function(chunk) {
write4(chunk.length);
write4(chunk.type);
writeData(chunk.data);
write4(chunk.crc)
});
return new Blob([buffer], {
'type': 'image/png'
})
};
function exportBlockAsPng(block) {
var xml = document.createElement('xml');
xml.appendChild(Blockly.Xml.blockToDom(block, true));
var code = Blockly.Xml.domToText(xml);
svgAsDataUri(block.svgGroup_, block.workspace.getMetrics(), null, function(uri) {
var img = new Image();
img.src = uri;
img.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = 2 * img.width;
canvas.height = 2 * img.height;
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
function download(png) {
png.setCodeChunk(code);
for (var i = 0; i < png.chunks.length; i++) {
var phy = [112, 72, 89, 115];
if (png.chunks[i].type == 'pHYs') {
png.chunks.splice(i, 1, new PNG.Chunk(9, 'pHYs', pHY_data, crc32(phy.concat(pHY_data))));
break
} else if (png.chunks[i].type == 'IDAT') {
png.chunks.splice(i, 0, new PNG.Chunk(9, 'pHYs', pHY_data, crc32(phy.concat(pHY_data))));
break
}
}
var blob = png.toBlob();
var a = document.createElement('a');
a.download = simpleString(block) + '.png';
a.target = '_self';
a.href = URL.createObjectURL(blob);
document.body.appendChild(a);
a.addEventListener("click", function(e) {
a.parentNode.removeChild(a)
});
a.click()
}
if (canvas.toBlob === undefined) {
var src = canvas.toDataURL('image/png');
var base64img = src.split(',')[1];
var decoded = window.atob(base64img);
var rawLength = decoded.length;
var buffer = new Uint8Array(new ArrayBuffer(rawLength));
for (var i = 0; i < rawLength; i++) {
buffer[i] = decoded.charCodeAt(i)
}
var blob = new Blob([buffer], {
'type': 'image/png'
});
new PNG().readFromBlob(blob, download)
} else {
canvas.toBlob(function(blob) {
new PNG().readFromBlob(blob, download)
})
}
}
})
};
console.log("block finder loaded~~~");
}, 8000);
})();