App Inventer 2 block helper

An easy way to operate blocks at MIT App Inventor 2.

Stan na 04-04-2023. Zobacz najnowsza wersja.

// ==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(/&nbsp/g, '&#160');
        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);

   

})();