From 4b74d5e92e81f40e1afbb56e549f03b2670bc731 Mon Sep 17 00:00:00 2001 From: rgantzos <86856959+rgantzos@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:20:12 -0800 Subject: [PATCH] New API: `scratchClass` --- api/main.js | 63 +++++++++++++++++++++-- api/module.js | 22 ++++++++ features/dark-paint-editor/script.js | 6 +-- features/delete-all.js | 2 +- features/echo-effect/script.js | 6 +-- features/go-to-parent/script.js | 2 +- features/last-key-pressed.js | 2 +- features/more-block-themes/script.js | 10 ++-- features/more-editor-fonts/script.js | 8 +-- features/more-paint-functions/script.js | 8 +-- features/move-project-title-input.js | 2 +- features/opacity-slider/script.js | 12 ++--- features/paint-align/script.js | 8 +-- features/rotate-gradient/script.js | 12 ++--- features/search-assets.js | 4 +- features/sprite-clones.js | 2 +- features/turbowarp-button-in-editor.js | 8 +-- features/video-recorder/video-recorder.js | 4 +- 18 files changed, 130 insertions(+), 51 deletions(-) diff --git a/api/main.js b/api/main.js index 6af72838..0100bca2 100644 --- a/api/main.js +++ b/api/main.js @@ -74,6 +74,24 @@ ScratchTools.Storage = {}; ScratchTools.Resources = {}; ste.console.log("ScratchTools API Created", "ste-main"); +ScratchTools.cssFiles = []; +async function updateCSSFiles() { + let activeCSSFiles = Array.from(document.styleSheets) + .filter((sheet) => sheet.href) + .map((sheet) => sheet.href) + .filter((el) => new URL(el).host === "scratch.mit.edu"); + activeCSSFiles = activeCSSFiles.filter( + (el) => !ScratchTools.cssFiles.find((e) => e.url === el) + ); + + for (var i in activeCSSFiles) { + ScratchTools.cssFiles.push({ + url: activeCSSFiles[i], + data: await (await fetch(activeCSSFiles[i])).text(), + }); + } +} + if ( window.location.href.startsWith("https://scratch.mit.edu/projects/") && window.location.href.includes("/editor") @@ -159,6 +177,7 @@ function enableScratchToolsSelectorsMutationObserver() { enableScratchToolsSelectorsMutationObserver(); function returnScratchToolsSelectorsMutationObserverCallbacks() { + updateCSSFiles() Object.keys(allWaitInstances).forEach(function (key) { var waitInstance = allWaitInstances[key]; if (!waitInstance.removed) { @@ -340,6 +359,39 @@ ScratchTools.styles = { }, }; +function scratchClass(name) { + let element = document.querySelector(`[class*='${name}']`); + if (element) { + let classes = [...element.classList]; + return classes.find((el) => el.includes(name)); + } else { + let text = [] + + for (var i in ScratchTools.cssFiles) { + text.push(ScratchTools.cssFiles[i].data) + } + + text = text.join("\n\n") + let classes = getClassNamesFromCSSText(text) + + let relClass = classes.find((el) => el.includes(name)) + return relClass + } +} + +ScratchTools.getClassNamesFromCSSText = function(cssText) { + const classNames = new Set(); + + const classRegex = /\.([a-zA-Z0-9_-]+)\b/g; + + let match; + while ((match = classRegex.exec(cssText)) !== null) { + classNames.add(match[1]); + } + + return Array.from(classNames); +} + ScratchTools.waitForElements( "ul[class*='menu_menu_'][class*='menu_right_']", function (ul) { @@ -351,7 +403,10 @@ ScratchTools.waitForElements( if (!ul.querySelector(".ste-menu-full-settings")) { var li = document.createElement("li"); li.className = - "ste-menu-full-settings menu_menu-item_3EwYA menu_hoverable_3u9dt"; + "ste-menu-full-settings " + + scratchClass("menu_menu-item_") + + " " + + scratchClass("menu_hoverable_"); var div = document.createElement("div"); div.className = "settings-menu_option_3rMur"; @@ -391,6 +446,8 @@ async function blockliveDetection() { Object.keys(app).find((key) => key.startsWith("__reactContainer")) ].child.stateNode.store.getState()?.scratchGui; if (!gui?.projectState) return; - let detectBlocklive = await import("./blocklive-detection/blocklive-detect.js"); + let detectBlocklive = await import( + "./blocklive-detection/blocklive-detect.js" + ); detectBlocklive.default(); -} \ No newline at end of file +} diff --git a/api/module.js b/api/module.js index 589f2731..a3fa58f0 100644 --- a/api/module.js +++ b/api/module.js @@ -1,6 +1,26 @@ let allFeatures = [] let alreadyInjected = []; +function scratchClass(name) { + let element = document.querySelector(`[class*='${name}']`); + if (element) { + let classes = [...element.classList]; + return classes.find((el) => el.includes(name)); + } else { + let text = [] + + for (var i in ScratchTools.cssFiles) { + text.push(ScratchTools.cssFiles[i].data) + } + + text = text.join("\n\n") + let classes = getClassNamesFromCSSText(text) + + let relClass = classes.find((el) => el.includes(name)) + return relClass + } +} + ScratchTools.modules.forEach(async function (script) { var feature = await import(ScratchTools.dir + "/api/feature/index.js"); var shouldBeRun = true; @@ -20,6 +40,7 @@ ScratchTools.modules.forEach(async function (script) { allFeatures.push(featureGenerated) fun.default({ feature: featureGenerated, + scratchClass, console: { log: function (content) { ste.console.log(content, script.feature.id); @@ -56,6 +77,7 @@ ScratchTools.injectModule = async function (script) { allFeatures.push(featureGenerated) fun.default({ feature: featureGenerated, + scratchClass, console: { log: function (content) { ste.console.log(content, script.feature.id); diff --git a/features/dark-paint-editor/script.js b/features/dark-paint-editor/script.js index 3461ade5..a466c715 100644 --- a/features/dark-paint-editor/script.js +++ b/features/dark-paint-editor/script.js @@ -1,4 +1,4 @@ -export default async function ({ feature, console }) { +export default async function ({ feature, console, scratchClass }) { let isDark; const BACKGROUND_LIGHT = "#FFFFFF"; @@ -18,10 +18,10 @@ export default async function ({ feature, console }) { if (document.querySelector(".ste-dark-paint-btn")) return; let button = document.createElement("div") - button.className = "button-group_button-group_2_h4y ste-dark-paint-btn" + button.className = `${scratchClass("button-group_button-group_2_")} ste-dark-paint-btn` let span = document.createElement("span") - span.className = "button_button_u6SE2 paint-editor_button-group-button_1I1tm" + span.className = `${scratchClass("button_button_")} ${scratchClass("paint-editor_button-group-button_")}` span.role = "button" button.appendChild(span) diff --git a/features/delete-all.js b/features/delete-all.js index 55a712f7..b5828a39 100644 --- a/features/delete-all.js +++ b/features/delete-all.js @@ -7,7 +7,7 @@ function checkForContextMenu() { ) { var div = document.createElement("div"); div.className = - "react-contextmenu-item context-menu_menu-item_3cioN context-menu_menu-item-bordered_29CJG context-menu_menu-item-danger_1tJg0 scratchtools deleteall"; + `react-contextmenu-item ${scratchClass("context-menu_menu-item_")} ${scratchClass("context-menu_menu-item-bordered_")} ${scratchClass("context-menu_menu-item-danger_")} scratchtools deleteall`; div.role = "menuitem"; div.tabindex = "-1"; div.arialDisabled = "false"; diff --git a/features/echo-effect/script.js b/features/echo-effect/script.js index 0c7eb933..1b77baf7 100644 --- a/features/echo-effect/script.js +++ b/features/echo-effect/script.js @@ -1,11 +1,11 @@ -export default function ({ feature, console }) { +export default function ({ feature, console, scratchClass }) { ScratchTools.waitForElements( "div[class^='sound-editor_row_'][class*='sound-editor_row-reverse_']", function (container) { if (container.querySelector(".ste-echo")) return; let button = document.createElement("div"); button.className = - "icon-button_container_278u5 sound-editor_effect-button_2zuzT ste-echo"; + `${scratchClass("icon-button_container_")} ${scratchClass("sound-editor_effect-button_")} ste-echo`; button.role = "button"; feature.self.hideOnDisable(button) @@ -20,7 +20,7 @@ export default function ({ feature, console }) { button.appendChild(img); let title = document.createElement("div"); - title.className = "icon-button_title_36ChS"; + title.className = scratchClass("icon-button_title_"); title.textContent = feature.msg("echo"); button.appendChild(title); diff --git a/features/go-to-parent/script.js b/features/go-to-parent/script.js index 65e01dc3..7928ba6e 100644 --- a/features/go-to-parent/script.js +++ b/features/go-to-parent/script.js @@ -54,7 +54,7 @@ if ( if (data.remix !== undefined) { if (data.remix.parent !== null) { var div = document.createElement("div"); - div.className = "menu-bar_menu-bar-item_oLDa- scratchtools remix"; + div.className = `${scratchClass("menu-bar_menu-bar-item_")} scratchtools remix`; div.innerHTML = `
Go to Parent
`; document.querySelectorAll("div").forEach(function (el) { if (el.className.includes("menu-bar_main-menu_")) { diff --git a/features/last-key-pressed.js b/features/last-key-pressed.js index 260e5e8a..348b543c 100644 --- a/features/last-key-pressed.js +++ b/features/last-key-pressed.js @@ -15,7 +15,7 @@ function addKeyPressed() { function addKeyPressedEditor() { var div = document.createElement("div"); - div.className = "menu-bar_file-group_1_CHX scratchtools navlastkey"; + div.className = `${scratchClass("menu-bar_file-group_1_")} scratchtools navlastkey`; div.innerHTML = ` No Key Pressed `; diff --git a/features/more-block-themes/script.js b/features/more-block-themes/script.js index 23f9c673..68ee6378 100644 --- a/features/more-block-themes/script.js +++ b/features/more-block-themes/script.js @@ -1,4 +1,4 @@ -export default async function ({ feature, console }) { +export default async function ({ feature, console, scratchClass }) { let CIRCLE = await (await fetch(feature.self.getResource("circle"))).text(); let COLORS = document.createElement("link") @@ -118,18 +118,18 @@ export default async function ({ feature, console }) { let li = document.createElement("li"); li.dataset.id = THEMES[i].id; - li.className = "menu_menu-item_3EwYA menu_hoverable_3u9dt ste-custom"; + li.className = `${scratchClass("menu_menu-item_")} ${scratchClass("menu_hoverable_")} ste-custom`; let div = document.createElement("div"); - div.className = "settings-menu_option_3rMur"; + div.className = scratchClass("settings-menu_option_"); let check = document.createElement("img"); - check.className = "settings-menu_check_3ssaq"; + check.className = scratchClass("settings-menu_check_"); check.src = feature.self.getResource("check"); let img = document.createElement("span"); img.innerHTML = CIRCLE.replaceAll("-fill", "-circle-fill" + THEMES[i].id).replaceAll("-stroke", "-circle-stroke-" + THEMES[i].id); - img.className = "settings-menu_icon_3QaRk"; + img.className = scratchClass("settings-menu_icon_"); let circleCSS = document.createElement("style"); circleCSS.textContent = css.replaceAll("-fill", "-circle-fill" + THEMES[i].id).replaceAll("-stroke", "-circle-stroke-" + THEMES[i].id) diff --git a/features/more-editor-fonts/script.js b/features/more-editor-fonts/script.js index 7efad503..62fedb90 100644 --- a/features/more-editor-fonts/script.js +++ b/features/more-editor-fonts/script.js @@ -1,4 +1,4 @@ -export default async function ({ feature, console }) { +export default async function ({ feature, console, scratchClass }) { let { default: openTypeDefault } = await import( "../../libraries/opentype.js" ); @@ -26,20 +26,20 @@ export default async function ({ feature, console }) { button.currentitem = false; button.ariaLabel = "Add Font"; button.className = - "action-menu_button_1qbot action-menu_more-button_1fMGZ ste-more-fonts-btn"; + `${scratchClass("action-menu_button_")} ${scratchClass("action-menu_more-button_")} ste-more-fonts-btn`; div.appendChild(button); let img = Object.assign(document.createElement("img"), { src: feature.self.getResource("more-text-icon"), draggable: false, - className: "action-menu_more-icon_TJUQ7", + className: scratchClass("action-menu_more-icon_"), width: 10, }); button.appendChild(img); let tooltip = Object.assign(document.createElement("div"), { className: - "__react_component_tooltip place-right type-dark action-menu_tooltip_3Bkh5", + `__react_component_tooltip place-right type-dark ${scratchClass("action-menu_tooltip_")}`, id: `ste-${id}-Add Font`, textContent: "Add Font", }); diff --git a/features/more-paint-functions/script.js b/features/more-paint-functions/script.js index ddfd0849..a753cf89 100644 --- a/features/more-paint-functions/script.js +++ b/features/more-paint-functions/script.js @@ -1,4 +1,4 @@ -export default async function ({ feature, console }) { +export default async function ({ feature, console, scratchClass }) { function unite() { let paper = feature.traps.getPaper(); let items = paper.project.selectedItems; @@ -133,12 +133,12 @@ export default async function ({ feature, console }) { function makeButton({ name, icon, callback }) { let span = document.createElement("span"); span.className = - "button_button_u6SE2 labeled-icon-button_mod-edit-field_1bXYC ste-more-functions"; + `${scratchClass("button_button_")} ${scratchClass("labeled-icon-button_mod-edit-field_")} ste-more-functions`; span.role = "button"; let img = document.createElement("img"); img.src = feature.self.getResource(icon); - img.className = "labeled-icon-button_edit-field-icon_3j-Pf"; + img.className = scratchClass("labeled-icon-button_edit-field-icon_"); img.alt = name; img.title = name; img.draggable = false; @@ -146,7 +146,7 @@ export default async function ({ feature, console }) { let label = document.createElement("span"); label.textContent = name; - label.className = "labeled-icon-button_edit-field-title_1ZoEV"; + label.className = scratchClass("labeled-icon-button_edit-field-title_"); span.appendChild(label); span.addEventListener("click", function (e) { diff --git a/features/move-project-title-input.js b/features/move-project-title-input.js index 828cf89e..73fcfecd 100644 --- a/features/move-project-title-input.js +++ b/features/move-project-title-input.js @@ -13,7 +13,7 @@ ScratchTools.waitForElements( if (!document.querySelector(".st-new-title-input") && !document.querySelector("span[class*='menu-bar_remix-button_']")) { var input = document.createElement("input"); input.className = - "input_input-form_l9eYg project-title-input_title-field_en5Gd menu-bar_title-field-growable_3qr4G"; + `${scratchClass("input_input-form_")} ${scratchClass("project-title-input_title-field_")} ${scratchClass("menu-bar_title-field-growable_")}`; input.value = window.newTitle || ScratchTools.Scratch.scratchGui().projectTitle; input.placeholder = "Title"; input.style.width = "100%"; diff --git a/features/opacity-slider/script.js b/features/opacity-slider/script.js index d65584c1..925109e4 100644 --- a/features/opacity-slider/script.js +++ b/features/opacity-slider/script.js @@ -1,4 +1,4 @@ -export default function ({ feature, console }) { +export default function ({ feature, console, scratchClass }) { ScratchTools.waitForElements(".Popover-body", function (body) { if (!feature.traps.paint().modals.fillColor) return; if (!feature.traps.paint().selectedItems[0]) return; @@ -11,15 +11,15 @@ export default function ({ feature, console }) { feature.self.hideOnDisable(div); let data = document.createElement("div"); - data.className = "color-picker_row-header_173LQ"; + data.className = scratchClass("color-picker_row-header_"); div.appendChild(data); let name = document.createElement("span"); - name.className = "color-picker_label-name_17igY"; + name.className = scratchClass("color-picker_label-name_"); name.textContent = "Opacity"; let value = document.createElement("span"); - value.className = "color-picker_label-readout_9vjb2"; + value.className = scratchClass("color-picker_label-readout_"); value.textContent = Math.floor( (feature.traps.paint().selectedItems[0]?.opacity || 1) * 100 )?.toString(); @@ -29,7 +29,7 @@ export default function ({ feature, console }) { let slider = document.createElement("div"); slider.className = - "ste-opacity-slider-checkered slider_container_o2aIb slider_last_10jvO"; + `ste-opacity-slider-checkered ${scratchClass("slider_container_")} ${scratchClass("slider_last_")}`; div.appendChild(slider); let sliderBg = document.createElement("div"); @@ -41,7 +41,7 @@ export default function ({ feature, console }) { let handle = document.createElement("div"); handleSlider(handle, value); - handle.className = "ste-opacity-handle slider_handle_3f0xk"; + handle.className = `ste-opacity-handle ${scratchClass("slider_handle_")}`; handle.style.left = "124px"; if (feature.traps.paint().selectedItems[0]?.opacity) { handle.style.left = diff --git a/features/paint-align/script.js b/features/paint-align/script.js index 15e83ddd..4b127091 100644 --- a/features/paint-align/script.js +++ b/features/paint-align/script.js @@ -1,4 +1,4 @@ -export default async function ({ feature }) { +export default async function ({ feature, scratchClass }) { ScratchTools.waitForElements( "div[class^='mode-tools_mod-labeled-icon-height_']", function (row) { @@ -6,12 +6,12 @@ export default async function ({ feature }) { let span = document.createElement("span"); span.className = - "button_button_u6SE2 labeled-icon-button_mod-edit-field_1bXYC ste-align-items"; + `${scratchClass("button_button_")} ${scratchClass("labeled-icon-button_mod-edit-field_")} ste-align-items`; span.role = "button"; let img = document.createElement("img"); img.src = feature.self.getResource("paint-align"); - img.className = "labeled-icon-button_edit-field-icon_3j-Pf"; + img.className = scratchClass("labeled-icon-button_edit-field-icon_"); img.alt = feature.msg("align"); img.title = feature.msg("align"); img.draggable = false; @@ -19,7 +19,7 @@ export default async function ({ feature }) { let label = document.createElement("span"); label.textContent = feature.msg("align"); - label.className = "labeled-icon-button_edit-field-title_1ZoEV"; + label.className = scratchClass("labeled-icon-button_edit-field-title_"); span.appendChild(label); span.addEventListener("click", function (e) { diff --git a/features/rotate-gradient/script.js b/features/rotate-gradient/script.js index 4aa736f3..a26a2ee1 100644 --- a/features/rotate-gradient/script.js +++ b/features/rotate-gradient/script.js @@ -1,4 +1,4 @@ -export default async function ({ feature, console }) { +export default async function ({ feature, console, scratchClass }) { let lastRotation = 0 feature.page.waitForElements( @@ -16,15 +16,15 @@ export default async function ({ feature, console }) { feature.self.hideOnDisable(div); let data = document.createElement("div"); - data.className = "color-picker_row-header_173LQ"; + data.className = scratchClass("color-picker_row-header_"); div.appendChild(data); let name = document.createElement("span"); - name.className = "color-picker_label-name_17igY"; + name.className = scratchClass("color-picker_label-name_"); name.textContent = feature.msg("direction"); let value = document.createElement("span"); - value.className = "color-picker_label-readout_9vjb2"; + value.className = scratchClass("color-picker_label-readout_"); value.textContent = "0"; data.appendChild(name); @@ -32,7 +32,7 @@ export default async function ({ feature, console }) { let slider = document.createElement("div"); slider.className = - "ste-direction-slider-checkered slider_container_o2aIb slider_last_10jvO"; + "ste-direction-slider-checkered " + scratchClass("slider_container_") + " " + scratchClass("slider_last_Ik11I"); div.appendChild(slider); let sliderBg = document.createElement("div"); @@ -44,7 +44,7 @@ export default async function ({ feature, console }) { let handle = document.createElement("div"); handleSlider(handle, value); - handle.className = "ste-direction-handle slider_handle_3f0xk"; + handle.className = "ste-direction-handle " + scratchClass("slider_handle_ubeAr"); handle.style.left = "0px"; slider.appendChild(handle); diff --git a/features/search-assets.js b/features/search-assets.js index afd13cf4..611d7e8e 100644 --- a/features/search-assets.js +++ b/features/search-assets.js @@ -5,7 +5,7 @@ if (document.querySelector('[class^="asset-panel_wrapper_"]')) { var input = document.createElement("input"); var assetBox = document.querySelector('[class^="asset-panel_wrapper_"]'); var assetRow = assetBox.firstChild.firstChild; - input.className = "scratchtoolsAssetSearch input_input-form_l9eYg"; + input.className = "scratchtoolsAssetSearch " + scratchClass("input_input-form_l9eYg"); input.placeholder = "Search"; input.type = "search"; input.autocomplete = "off"; @@ -40,7 +40,7 @@ ScratchTools.waitForElements( if (!document.querySelector(".scratchtoolsAssetSearch") && showSearchBar) { var input = document.createElement("input"); var assetRow = assetBox.firstChild.firstChild; - input.className = "scratchtoolsAssetSearch input_input-form_l9eYg"; + input.className = "scratchtoolsAssetSearch " + scratchClass("input_input-form_"); input.placeholder = "Search"; input.type = "search"; input.autocomplete = "off"; diff --git a/features/sprite-clones.js b/features/sprite-clones.js index 8bf19d82..7f6f7e5a 100644 --- a/features/sprite-clones.js +++ b/features/sprite-clones.js @@ -19,7 +19,7 @@ if ( if (el.className.startsWith("sprite-info_row_")) { foundIt = true; var div = document.createElement("div"); - div.className = "sprite-info_group_14-B_"; + div.className = scratchClass("sprite-info_group_"); div.innerHTML = ``; diff --git a/features/turbowarp-button-in-editor.js b/features/turbowarp-button-in-editor.js index 249d3482..9039f8cc 100644 --- a/features/turbowarp-button-in-editor.js +++ b/features/turbowarp-button-in-editor.js @@ -8,7 +8,7 @@ if ( waitForNavForTurbowarp.disconnect(); var outerDiv = document.createElement("div"); outerDiv.className = - "menu-bar_menu-bar-item_oLDa- scratchtoolsTurbowarp"; + scratchClass("menu-bar_menu-bar-item_") + " scratchtoolsTurbowarp"; var a = document.createElement("a"); a.addEventListener("click", async function () { let projectToken = ( @@ -35,17 +35,17 @@ if ( }); var outerSpan = document.createElement("span"); outerSpan.className = - "button_outlined-button_1bS__ menu-bar_menu-bar-button_3IDN0 community-button_community-button_2Lo_g"; + `${scratchClass("button_outlined-button_")} ${scratchClass("menu-bar_menu-bar-button_")} ${scratchClass("community-button_community-button_")}`; outerSpan.role = "button"; var img = document.createElement("img"); img.draggable = false; img.src = "https://dashboard.snapcraft.io/site_media/appmedia/2021/02/512x512_Q3PveGU.png"; img.className = - "community-button_community-button-icon_1IFvv button_icon_77d8G"; + `${scratchClass("community-button_community-button-icon_")} ${scratchClass("button_icon_")}`; outerSpan.appendChild(img); var innerDiv = document.createElement("div"); - innerDiv.className = "button_content_3jdgj"; + innerDiv.className = scratchClass("button_content_"); var innerSpan = document.createElement("span"); innerSpan.style.color = "white"; innerSpan.textContent = "Open in TurboWarp"; diff --git a/features/video-recorder/video-recorder.js b/features/video-recorder/video-recorder.js index ccba54cd..d43b84b5 100644 --- a/features/video-recorder/video-recorder.js +++ b/features/video-recorder/video-recorder.js @@ -1,4 +1,4 @@ -export default async function ({ feature, console }) { +export default async function ({ feature, console, scratchClass }) { await new Promise(async (resolve, reject) => { (async () => { const rem = await ScratchTools.waitForElement(".preview .inner .flex-row.action-buttons") @@ -26,7 +26,7 @@ export default async function ({ feature, console }) { ScratchTools.waitForElements(".menu-bar_account-info-group_MeJZP", async function (row) { if (row.querySelector(".ste-video-recorder-open")) return; openPopup = document.createElement("div"); - openPopup.className = "menu-bar_menu-bar-item_oLDa- menu-bar_hoverable_c6WFB"; + openPopup.className = `${scratchClass("menu-bar_menu-bar-item_")} ${scratchClass("menu-bar_hoverable_")}`; openPopup.style.padding = "0 0.75rem" let rem = document.createElement("div"); rem.textContent = "Record Video";