// ==UserScript==
// @name scratch - show unused blocks
// @version 0.4
// @description adds a unused blocks tab to the debug panel of scratch addons that shows all custom blocks that exist but are not called and all that call to a nonexistant custom block
// @license GPLv3
// @run-at document-start
// @author You
// @match *://scratch.mit.edu/*
// @icon https://scratch.mit.edu/favicon.ico
// @grant none
// @namespace https://greasyfork.org/users/1184528
// ==/UserScript==
;(async () => {
const a = loadlib('allfuncs')
const {vm} = loadlib('scratch')
await a(".sa-debugger-tabs>li").waitforelem()
var unusedblocks = a(".sa-debugger-tabs").createelem("li", {
class: "scratchCategoryId-myBlocks",
innerHTML: `<div class="scratchCategoryItemBubble" style="background-color: rgb(94, 30, 63); border-color: rgb(255, 0, 132);"></div>unused blocks`,
onclick(e) {
e.stopImmediatePropagation()
a(".sa-debugger-tab-selected")
.qs()
.val.classList.remove("sa-debugger-tab-selected")
this.classList.add("sa-debugger-tab-selected")
var content = a(".sa-debugger-tab-content").qs().val
content.innerHTML = ""
var arrs = {}
vm.runtime.targets.forEach((target) =>
Object.entries(target.blocks._blocks)
.filter(
(target) =>
target[1].opcode == "procedures_call" ||
target[1].opcode == "procedures_prototype"
)
.forEach((e) => {
if (!arrs[target.sprite.name])
arrs[target.sprite.name] = {
procedures_prototype: [],
procedures_call: [],
}
arrs[target.sprite.name][e[1].opcode].push(e[1]?.mutation?.proccode)
})
)
var parents = {}
Object.entries(arrs).forEach(
([name, { procedures_call, procedures_prototype }]) => {
procedures_call.forEach((call) => {
if (!procedures_prototype.includes(call)) {
if (
[
"breakpoint",
"warn %s",
"log %s",
"error %s",
"breakpoint",
].includes(call)
)
return
var parent =
parents[name] ??
a(content).createelem("div", {
id: "ubpar" + name,
width: "calc(100% - 8px)",
border:
vm.editingTarget.sprite.name == name
? "4px solid #090"
: "4px solid #900",
backgroundColor:
vm.editingTarget.sprite.name == name ? "#070" : "#700",
innerHTML: `${name}`,
})
parents[name] ??= parent
a(parent).createelem("div", {
width: "calc(100% - 48px)",
position: "relative",
left: "40px",
border: "4px solid #990",
backgroundColor: "#770",
innerHTML: `call to non existent function "${call}"`,
onclick() {
if (vm.editingTarget.sprite.name == name)
goto("define " + call)
},
})
}
})
procedures_prototype.forEach((prototype) => {
if (!procedures_call.includes(prototype)) {
var parent =
parents[name] ??
a(content).createelem("div", {
id: "ubpar" + name,
width: "calc(100% - 8px)",
border:
vm.editingTarget.sprite.name == name
? "4px solid #090"
: "4px solid #900",
backgroundColor:
vm.editingTarget.sprite.name == name ? "#070" : "#700",
innerHTML: `${name}`,
})
parents[name] ??= parent
a(parent).createelem("div", {
width: "calc(100% - 48px)",
position: "relative",
left: "40px",
border: "4px solid #990",
backgroundColor: "#770",
innerHTML: `unused function "${prototype}"`,
onclick() {
if (vm.editingTarget.sprite.name == name)
goto("define " + prototype)
},
})
}
})
}
)
},
}).val
a(".sa-debugger-tabs>li")
.qsa()
.map((e) =>
a(e).listen("click", function (e) {
log(e.target)
if (e.target !== unusedblocks)
unusedblocks.classList.remove("sa-debugger-tab-selected")
})
)
function goto(text) {
var find = a("#sa-find-input").qs().val
find.value = text
// find.blur()
find.focus()
a('.sa-find-dropdown>li[style="display: block;"]')
.qs()
.val.dispatchEvent(
new MouseEvent("mousedown", {
bubbles: true,
cancelable: true,
view: window,
})
)
}
})()