diff --git a/extensions/SharkPool/Better-Input.js b/extensions/SharkPool/Better-Input.js new file mode 100644 index 0000000000..63134100e9 --- /dev/null +++ b/extensions/SharkPool/Better-Input.js @@ -0,0 +1,1324 @@ +// Name: Better Input +// ID: BetterInputSP +// Description: Expansion of the "ask and wait" Blocks. +// By: SharkPool +// Licence: MLP-2.0 + +// Version V.4.2.11 + +(function (Scratch) { + "use strict"; + if (!Scratch.extensions.unsandboxed) throw new Error("Better Input must run unsandboxed"); + + const menuIconURI = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzQuMTc2IiBoZWlnaHQ9IjEzNC4xNzYiIHZpZXdCb3g9IjAgMCAxMzQuMTc2IDEzNC4xNzYiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgNjcuMDg4QzAgMzAuMDM2IDMwLjAzNiAwIDY3LjA4OCAwczY3LjA4OCAzMC4wMzYgNjcuMDg4IDY3LjA4OC0zMC4wMzYgNjcuMDg4LTY3LjA4OCA2Ny4wODhTMCAxMDQuMTQgMCA2Ny4wODgiIGZpbGw9IiM5NDAwZmYiLz48cGF0aCBkPSJtNzYuNzY3IDI4LjEzNCAxMC44MzQuMDRjMTAuNTE1LjA4IDEwLjgzNC4xMiAxMS41MS41OTcgMi40MyAxLjg3MiAyLjM5IDQuOTM5LS4xNTkgNi42NTItLjc1Ni41MTgtMS4xMTUuNTU3LTQuMzQxLjU1N2gtMy41MDV2NjIuMTc2aDMuNDY1YzMuOTQzIDAgNC45LjMxOCA1LjgxNSAxLjk1MSAxLjAzNiAxLjgzMy41MTggMy45NDQtMS4yNzQgNS4yNTgtLjY3Ny41MTgtLjk5Ni41MTgtMTEuNTExLjU5OGwtMTAuODM0LjA4LTEuMTE1LS41NThjLTEuMTU2LS42MzgtMi4xOTEtMi4xNTEtMi4xOTEtMy4zODYgMC0uODc2Ljk5Ni0yLjY2OSAxLjc5Mi0zLjI2Ni41OTgtLjQ3OCAxLjExNi0uNTE4IDQuMzQyLS41OThsMy42NjQtLjA4VjM2LjAybC0zLjY2NC0uMDhjLTMuMjI2LS4wOC0zLjc0NC0uMTE5LTQuMzQyLS41OTctLjc5Ni0uNTU4LTEuNzkyLTIuMzUtMS43OTItMy4yNjYgMC0xLjIzNS45OTYtMi43ODggMi4xOS0zLjM4NnoiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzY5MDBiNCIgc3Ryb2tlLXdpZHRoPSIxMCIvPjxwYXRoIGQ9Ik01MS43NTMgNDUuNTRoMjguMDR2NDMuMDU2bC0yOC40MzgtLjA0Yy0yOC4yLS4wNzktMjguNDM5LS4wNzktMjkuMzE1LS41OTctLjQ3OC0uMjc5LTEuMTE2LS45MTYtMS4zOTQtMS40NzRsLS41NTgtLjk1NVY2Ny4wODhjMC0xNy43MjUuMDQtMTguNDQyLjQ3OC0xOS4zNTguNTk3LTEuMDc1Ljk1Ni0xLjQzNCAyLjE1LTEuODcyLjc1OC0uMjM5IDYuMTc1LS4zMTggMjkuMDM3LS4zMTh6bS0xMC4wMzcgOS45OTdjLS42MzcuMzk4LTEuMzE0IDEuNzkzLTUuMDE5IDkuOTk4LTQuNjIgMTAuMjM2LTQuODU5IDEwLjkxMy0zLjgyMyAxMi4wMjggMS4xNTUgMS4yMzUgMi45NDcgMS4yNzUgMy45ODMuMDQuMzE4LS4zOTguNzk2LTEuMjc0IDEuMDM1LTEuOTUxbC40MzgtMS4yMzVoOS41NmwuNTU3IDEuNDM0Yy43MTcgMS45NTEgMS40NzQgMi42MjkgMi45MDggMi42MjkuOTE2IDAgMS4xOTUtLjEyIDEuNzkyLS43OTcuNTE4LS41NTguNzE3LTEuMDM2LjcxNy0xLjY3M3MtMS4xNTUtMy41ODUtMy43ODQtOS40NGMtNC45NzgtMTEuMTkyLTQuNzQtMTAuNzE0LTUuNTM2LTExLjExMi0uOTE2LS41MTgtMS45MTItLjQ3OC0yLjgyOC4wOHoiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzY5MDBiNCIgc3Ryb2tlLXdpZHRoPSIxMCIvPjxwYXRoIGQ9Ik00My4xOSA2My4xODVjLjE1OS4xNTkgMi41ODkgNi4xMzQgMi41ODkgNi4zMzMgMCAuMTU5LTEuMTk1LjIzOS0yLjcwOS4yMzktMi4wNzEgMC0yLjY2OC0uMDgtMi41ODktLjMxOS4wOC0uMTYuNjc3LTEuNjMzIDEuMzk0LTMuMzA2LjcxNy0xLjYzMyAxLjMxNS0yLjk4NyAxLjMxNS0yLjk0N3ptNTEuNTQgMy45MDNWNDUuNWw4LjI4NS4wOGM3LjgwNy4wOCA4LjMyNS4xMTkgOS4xMjEuNTk3LjQ3OC4yNzkgMS4xMTYuOTE2IDEuMzk0IDEuNDc0bC41NTguOTU2Vjg1LjUzbC0uNTU4Ljk1NWMtLjI3OC41NTgtLjkxNiAxLjE5NS0xLjM5NCAxLjQ3NC0uODM2LjUxOC0xLjMxNC41MTgtOS4xMi41OThsLTguMjg2LjA4eiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjkwMGI0IiBzdHJva2Utd2lkdGg9IjEwIi8+PHBhdGggZD0ibTc2Ljc2NyAyOC4xMzQgMTAuODM0LjA0YzEwLjUxNS4wOCAxMC44MzQuMTIgMTEuNTEuNTk3IDIuNDMgMS44NzIgMi4zOSA0LjkzOS0uMTU5IDYuNjUyLS43NTYuNTE4LTEuMTE1LjU1Ny00LjM0MS41NTdoLTMuNTA1djYyLjE3NmgzLjQ2NWMzLjk0MyAwIDQuOS4zMTggNS44MTUgMS45NTEgMS4wMzYgMS44MzMuNTE4IDMuOTQ0LTEuMjc0IDUuMjU4LS42NzcuNTE4LS45OTYuNTE4LTExLjUxMS41OThsLTEwLjgzNC4wOC0xLjExNS0uNTU4Yy0xLjE1Ni0uNjM4LTIuMTkxLTIuMTUxLTIuMTkxLTMuMzg2IDAtLjg3Ni45OTYtMi42NjkgMS43OTItMy4yNjYuNTk4LS40NzggMS4xMTYtLjUxOCA0LjM0Mi0uNTk4bDMuNjY0LS4wOFYzNi4wMmwtMy42NjQtLjA4Yy0zLjIyNi0uMDgtMy43NDQtLjExOS00LjM0Mi0uNTk3LS43OTYtLjU1OC0xLjc5Mi0yLjM1LTEuNzkyLTMuMjY2IDAtMS4yMzUuOTk2LTIuNzg4IDIuMTktMy4zODZ6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTc4LjQ2MSA0NS41NHY0My4wNTZzLTU1LjU0NS0uMTE5LTU2LjQyMS0uNjM3Yy0uNDc4LS4yNzktMS4xMTYtLjkxNi0xLjM5NC0xLjQ3NGwtLjU1OC0uOTU1VjY3LjA4OGMwLTE3LjcyNS4wNC0xOC40NDIuNDc4LTE5LjM1OC41OTctMS4wNzUuOTU2LTEuNDM0IDIuMTUtMS44NzIuNzU4LS4yMzkgNTUuNzQ1LS4zMTggNTUuNzQ1LS4zMThtLTM2Ljc0NSA5Ljk5N2MtLjYzNy4zOTgtMS4zMTQgMS43OTMtNS4wMTkgOS45OTgtNC42MiAxMC4yMzYtNC44NTkgMTAuOTEzLTMuODIzIDEyLjAyOCAxLjE1NSAxLjIzNSAyLjk0NyAxLjI3NSAzLjk4My4wNC4zMTgtLjM5OC43OTYtMS4yNzQgMS4wMzUtMS45NTFsLjQzOC0xLjIzNWg5LjU2bC41NTcgMS40MzRjLjcxNyAxLjk1MSAxLjQ3NCAyLjYyOSAyLjkwOCAyLjYyOS45MTYgMCAxLjE5NS0uMTIgMS43OTItLjc5Ny41MTgtLjU1OC43MTctMS4wMzYuNzE3LTEuNjczcy0xLjE1NS0zLjU4NS0zLjc4NC05LjQ0Yy00Ljk3OC0xMS4xOTItNC43NC0xMC43MTQtNS41MzYtMTEuMTEyLS45MTYtLjUxOC0xLjkxMi0uNDc4LTIuODI4LjA4IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTQzLjE5IDYzLjE4NWMuMTU5LjE1OSAyLjU4OSA2LjEzNCAyLjU4OSA2LjMzMyAwIC4xNTktMS4xOTUuMjM5LTIuNzA5LjIzOS0yLjA3MSAwLTIuNjY4LS4wOC0yLjU4OS0uMzE5LjA4LS4xNi42NzctMS42MzMgMS4zOTQtMy4zMDYuNzE3LTEuNjMzIDEuMzE1LTIuOTg3IDEuMzE1LTIuOTQ3TTk2LjA2NCA0NS41czE1LjI3Ni4xOTkgMTYuMDcyLjY3N2MuNDc4LjI3OSAxLjExNi45MTYgMS4zOTQgMS40NzRsLjU1OC45NTZWODUuNTNsLS41NTguOTU1Yy0uMjc4LjU1OC0uOTE2IDEuMTk1LTEuMzk0IDEuNDc0LS44MzYuNTE4LTE2LjA3Mi42NzctMTYuMDcyLjY3N3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; + const blockIconURI = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OS4zNjMiIGhlaWdodD0iODcuOTIxIiB2aWV3Qm94PSIwIDAgNzkuMzYzIDg3LjkyMSI+PGcgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIj48cGF0aCBkPSJNMzIuMzUzIDQzLjk1OXYyMS41NDhsLTExLjcwNi0uMDhjLTExLjAzMS0uMDc5LTExLjcwNy0uMDc5LTEyLjg4OS0uNTk3LS42NzUtLjI3OS0xLjU3Ni0uOTE2LTEuOTctMS40NzRMNSA2Mi40MDFWMjUuNDc4bC43ODgtLjk1NmMuMzk0LS41NTggMS4yOTUtMS4xOTUgMS45Ny0xLjQ3NCAxLjEyNi0uNDc4IDEuODU4LS41MTggMTIuODg5LS41OTdsMTEuNzA2LS4wOHoiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzY5MDBiNCIgc3Ryb2tlLXdpZHRoPSIxMCIvPjxwYXRoIGQ9Ik0zMC40NjkgNjUuNTA3cy0yMS41MjktLjE1OS0yMi43MS0uNjc3Yy0uNjc2LS4yNzktMS41NzctLjkxNi0xLjk3LTEuNDc0TDUgNjIuNDAxVjI1LjQ3OGwuNzg4LS45NTZjLjM5NC0uNTU4IDEuMjk1LTEuMTk1IDEuOTctMS40NzQgMS4xMjYtLjQ3OCAyMi43MTEtLjY3NyAyMi43MTEtLjY3N3oiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNNDcuMDExIDQzLjk1OVYyMi4zNzFsMTEuNzA2LjA4YzExLjAzMS4wOCAxMS43NjMuMTE5IDEyLjg4OS41OTcuNjc1LjI3OSAxLjU3Ni45MTYgMS45NyAxLjQ3NGwuNzg4Ljk1NnYzNi45MjNsLS43ODguOTU1Yy0uMzk0LjU1OC0xLjI5NSAxLjE5NS0xLjk3IDEuNDc0LTEuMTgyLjUxOC0xLjg1OC41MTgtMTIuODg5LjU5OGwtMTEuNzA2LjA4eiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjkwMGI0IiBzdHJva2Utd2lkdGg9IjEwIi8+PHBhdGggZD0iTTQ4Ljg5NSAyMi4zNzFzMjEuNTg1LjE5OSAyMi43MS42NzdjLjY3Ni4yNzkgMS41NzcuOTE2IDEuOTcgMS40NzRsLjc4OS45NTZ2MzYuOTIzbC0uNzg4Ljk1NWMtLjM5NC41NTgtMS4yOTUgMS4xOTUtMS45NyAxLjQ3NC0xLjE4Mi41MTgtMjIuNzExLjY3Ny0yMi43MTEuNjc3eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Im0yOS4yNzggNS4wMDUgMTAuODMzLjA0YzEwLjUxNi4wOCAxMC44MzQuMTIgMTEuNTExLjU5NyAyLjQzIDEuODcyIDIuMzkgNC45MzktLjE1OSA2LjY1Mi0uNzU3LjUxOC0xLjExNS41NTctNC4zNDEuNTU3aC0zLjUwNXY2Mi4xNzZoMy40NjVjMy45NDMgMCA0Ljg5OS4zMTggNS44MTUgMS45NTEgMS4wMzYgMS44MzMuNTE4IDMuOTQ0LTEuMjc1IDUuMjU4LS42NzcuNTE4LS45OTUuNTE4LTExLjUxLjU5OGwtMTAuODM0LjA4LTEuMTE2LS41NThjLTEuMTU1LS42MzgtMi4xOS0yLjE1MS0yLjE5LTMuMzg2IDAtLjg3Ni45OTUtMi42NjkgMS43OTItMy4yNjYuNTk3LS40NzggMS4xMTUtLjUxOCA0LjM0Mi0uNTk4bDMuNjY0LS4wOFYxMi44OTFsLTMuNjY0LS4wOGMtMy4yMjctLjA4LTMuNzQ1LS4xMTktNC4zNDItLjU5Ny0uNzk3LS41NTgtMS43OTItMi4zNS0xLjc5Mi0zLjI2NiAwLTEuMjM1Ljk5NS0yLjc4OCAyLjE5LTMuMzg2eiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjkwMGI0IiBzdHJva2Utd2lkdGg9IjEwIi8+PHBhdGggZD0ibTI5LjI3OCA1LjAwNSAxMC44MzMuMDRjMTAuNTE2LjA4IDEwLjgzNC4xMiAxMS41MTEuNTk3IDIuNDMgMS44NzIgMi4zOSA0LjkzOS0uMTU5IDYuNjUyLS43NTcuNTE4LTEuMTE1LjU1Ny00LjM0MS41NTdoLTMuNTA1djYyLjE3NmgzLjQ2NWMzLjk0MyAwIDQuODk5LjMxOCA1LjgxNSAxLjk1MSAxLjAzNiAxLjgzMy41MTggMy45NDQtMS4yNzUgNS4yNTgtLjY3Ny41MTgtLjk5NS41MTgtMTEuNTEuNTk4bC0xMC44MzQuMDgtMS4xMTYtLjU1OGMtMS4xNTUtLjYzOC0yLjE5LTIuMTUxLTIuMTktMy4zODYgMC0uODc2Ljk5NS0yLjY2OSAxLjc5Mi0zLjI2Ni41OTctLjQ3OCAxLjExNS0uNTE4IDQuMzQyLS41OThsMy42NjQtLjA4VjEyLjg5MWwtMy42NjQtLjA4Yy0zLjIyNy0uMDgtMy43NDUtLjExOS00LjM0Mi0uNTk3LS43OTctLjU1OC0xLjc5Mi0yLjM1LTEuNzkyLTMuMjY2IDAtMS4yMzUuOTk1LTIuNzg4IDIuMTktMy4zODZ6IiBmaWxsPSIjZmZmIi8+PC9nPjwvc3ZnPg=="; + + const formatIcon = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTAuOTA0IiBoZWlnaHQ9Ijk1LjUiIHZpZXdCb3g9IjAgMCAxNTAuOTA0IDk1LjUiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTc1LjE5NCA2OS41NjhzLTQwLjI2Ny0uMTYtNDEuNTYtLjY3N2MtLjczOC0uMjc5LTEuNzIyLS45MTYtMi4xNTMtMS40NzRsLS44NjItLjk1NlYyOS41NGwuODYyLS45NTZjLjQzLS41NTggMS40MTUtMS4xOTUgMi4xNTQtMS40NzQgMS4yMy0uNDc4IDQxLjU1OS0uNjc3IDQxLjU1OS0uNjc3eiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjkwMGI0IiBzdHJva2Utd2lkdGg9IjEwIi8+PHBhdGggZD0iTTY5LjU0MyAyNi40MzJzNDIuNjYyLjE5OSA0My44OTMuNjc3Yy43MzguMjc5IDEuNzIzLjkxNiAyLjE1NCAxLjQ3NGwuODYxLjk1NnYzNi45MjJsLS44NjEuOTU2Yy0uNDMuNTU4LTEuNDE2IDEuMTk1LTIuMTU0IDEuNDc0LTEuMjkyLjUxOC00My44OTMuNjc3LTQzLjg5My42Nzd6IiBmaWxsPSJub25lIiBzdHJva2U9IiM2OTAwYjQiIHN0cm9rZS13aWR0aD0iMTAiLz48cGF0aCBkPSJNNzEuNjAzIDI2LjQzMnM0MC42MDIuMTk5IDQxLjgzMy42NzdjLjczOC4yNzkgMS43MjMuOTE2IDIuMTU0IDEuNDc0bC44NjEuOTU2djM2LjkyMmwtLjg2MS45NTZjLS40My41NTgtMS40MTYgMS4xOTUtMi4xNTQgMS40NzQtMS4yOTIuNTE4LTQxLjgzMy42NzctNDEuODMzLjY3N3oiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNNzMuMTM0IDY5LjU2OHMtMzguMjA3LS4xNi0zOS41LS42NzdjLS43MzgtLjI3OS0xLjcyMi0uOTE2LTIuMTUzLTEuNDc0bC0uODYyLS45NTZWMjkuNTRsLjg2Mi0uOTU2Yy40My0uNTU4IDEuNDE1LTEuMTk1IDIuMTU0LTEuNDc0IDEuMjMtLjQ3OCAzOS40OTktLjY3NyAzOS40OTktLjY3N3oiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNNSAyMi4xNThWNWgxNy4xNTh6IiBmaWxsPSJub25lIiBzdHJva2U9IiM2OTAwYjQiIHN0cm9rZS13aWR0aD0iMTAiLz48cGF0aCBkPSJNMTQ1LjkwNCA3My4zNDJWOTAuNWgtMTcuMTU4eiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjkwMGI0IiBzdHJva2Utd2lkdGg9IjEwIi8+PHBhdGggZD0iTTUgMjIuMTU4VjVoMTcuMTU4eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xNDUuOTA0IDczLjM0MlY5MC41aC0xNy4xNTh6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTIyLjE1OCA5MC41SDVWNzMuMzQyek0xMjguNzQ2IDVoMTcuMTU4djE3LjE1OHoiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzY5MDBiNCIgc3Ryb2tlLXdpZHRoPSIxMCIvPjxwYXRoIGQ9Ik0yMi4xNTggOTAuNUg1VjczLjM0MnpNMTI4Ljc0NiA1aDE3LjE1OHYxNy4xNTh6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTcxLjcxOCAzMy40MmMxLjE4My0uNzIgMi40ODUtLjc3MSAzLjY2Ny0uMTAzIDEuMDI5LjUxNC43NzctLjA0IDcuMjA1IDE0LjQwOSAzLjM5NCA3LjU1OSA0Ljg3IDExLjQwOCA0Ljg3IDEyLjIzMXMtLjI2MyAxLjQ0NS0uOTMyIDIuMTY1Yy0uNzcuODc1LTEuMTM3IDEuMDM0LTIuMzIgMS4wMzQtMS44NTEgMC0yLjg5OC0uOTgyLTMuODI0LTMuNTAybC0uNjUzLTEuNzQxLTYuMjA1LjAxLTYuMjA1LS4wMS0uNDk4IDEuNDg0Yy0uMzA5Ljg3NC0uOTkxIDIuMTE3LTEuNDAzIDIuNjMtMS4zMzcgMS41OTUtMy42NjMgMS41MzQtNS4xNTQtLjA2LTEuMzM3LTEuNDQtMS4wNDctMi4zNzMgNC45MTgtMTUuNTg5IDQuNzgyLTEwLjU5MyA1LjcxMi0xMi40NDQgNi41MzQtMTIuOTU4IiBmaWxsPSIjNjkwMGI0Ii8+PHBhdGggZD0iTTczLjYyOSA0My4zNTdjLjIwNi4yMDYgMy4zNDMgNy45MiAzLjM0MyA4LjE3NiAwIC4yMDYtMS41NDMuMzA5LTMuNDk3LjMwOS0yLjY3NCAwLTMuNDQ2LS4xMDMtMy4zNDMtLjQxMi4xMDMtLjIwNS44NzQtMi4xMDggMS44LTQuMjY4LjkyNi0yLjEwOCAxLjY5Ny0zLjg1NiAxLjY5Ny0zLjgwNSIgZmlsbD0iI2ZmZiIvPjwvZz48L3N2Zz4="; + const colorIcon = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NS41MzUiIGhlaWdodD0iNzEuNDk3IiB2aWV3Qm94PSIwIDAgNzUuNTM1IDcxLjQ5NyI+PGcgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIj48cGF0aCBkPSJNMjEuMzA3IDIxLjQ2MkMyMS4zMDcgMTIuMzcgMjguNjc3IDUgMzcuNzY4IDVzMTYuNDYxIDcuMzcgMTYuNDYxIDE2LjQ2Mi03LjM3IDE2LjQ2MS0xNi40NjEgMTYuNDYxLTE2LjQ2MS03LjM3LTE2LjQ2MS0xNi40NjF6IiBmaWxsPSJub25lIiBzdHJva2U9IiM2OTAwYjQiIHN0cm9rZS13aWR0aD0iMTAiLz48cGF0aCBkPSJNNSA1MC4wMzZjMC05LjA5MSA3LjM3LTE2LjQ2MSAxNi40NjItMTYuNDYxczE2LjQ2MSA3LjM3IDE2LjQ2MSAxNi40NjFjMCA5LjA5Mi03LjM3IDE2LjQ2Mi0xNi40NjEgMTYuNDYyQzEyLjM3IDY2LjQ5OCA1IDU5LjEyOCA1IDUwLjAzNnoiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzY5MDBiNCIgc3Ryb2tlLXdpZHRoPSIxMCIvPjxwYXRoIGQ9Ik0zNy42MTMgNTAuMDM2YzAtOS4wOTEgNy4zNy0xNi40NjEgMTYuNDYxLTE2LjQ2MSA5LjA5MiAwIDE2LjQ2MiA3LjM3IDE2LjQ2MiAxNi40NjEgMCA5LjA5Mi03LjM3IDE2LjQ2Mi0xNi40NjIgMTYuNDYycy0xNi40NjEtNy4zNy0xNi40NjEtMTYuNDYyeiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjkwMGI0IiBzdHJva2Utd2lkdGg9IjEwIi8+PHBhdGggZD0iTTIxLjMwNyAyMS40NjJDMjEuMzA3IDEyLjM3IDI4LjY3NyA1IDM3Ljc2OCA1czE2LjQ2MSA3LjM3IDE2LjQ2MSAxNi40NjItNy4zNyAxNi40NjEtMTYuNDYxIDE2LjQ2MS0xNi40NjEtNy4zNy0xNi40NjEtMTYuNDYxIiBmaWxsPSJsaW1lIi8+PHBhdGggZD0iTTUgNTAuMDM2YzAtOS4wOTEgNy4zNy0xNi40NjEgMTYuNDYyLTE2LjQ2MXMxNi40NjEgNy4zNyAxNi40NjEgMTYuNDYxYzAgOS4wOTItNy4zNyAxNi40NjItMTYuNDYxIDE2LjQ2MkMxMi4zNyA2Ni40OTggNSA1OS4xMjggNSA1MC4wMzYiIGZpbGw9InJlZCIvPjxwYXRoIGQ9Ik0zNy42MTMgNTAuMDM2YzAtOS4wOTEgNy4zNy0xNi40NjEgMTYuNDYxLTE2LjQ2MSA5LjA5MiAwIDE2LjQ2MiA3LjM3IDE2LjQ2MiAxNi40NjEgMCA5LjA5Mi03LjM3IDE2LjQ2Mi0xNi40NjIgMTYuNDYycy0xNi40NjEtNy4zNy0xNi40NjEtMTYuNDYyIiBmaWxsPSJibHVlIi8+PC9nPjwvc3ZnPg=="; + const effectIcon = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1Ny40NDIiIGhlaWdodD0iNzAuNDUiIHZpZXdCb3g9IjAgMCA1Ny40NDIgNzAuNDUiPjxnIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIj48cGF0aCBkPSJNNSA0OS4wNDdjLTMuOTg4LTguMTE3LTMuOTk2LTE4LjI0My4xNTMtMjYuNDFDOS44NCAxMy4wNjIgMTcuNzMgOC44MTggMjAuOSA3LjQzOGEzMS40IDMxLjQgMCAwIDEgNi4wNzYtMi4wODNsMi4xMi0uNDU3YzEuNTU4LS4zMiAzLjExLjY0NiAzLjQ0NyAyLjE4Ny4zNCAxLjU0NS0uNjUxIDMuMDY2LTIuMjE2IDMuNDAybC0yLjA2My40NDVhMjUuNCAyNS40IDAgMCAwLTQuOTYgMS43MTZjLTIuMDgzLjkwMi04Ljk3NCA0LjM5Mi0xMi45NDkgMTIuNTE1LTMuMDE3IDUuOTMzLTMuOTE3IDE1LjM2Ni44MzIgMjMuMTUgNC4zMDMgNy40OTggMTMuMTEyIDEyLjA5NSAyMS40NjggMTEuMzIxIDcuOTAzLS41NjggMTUuMTU1LTUuODI4IDE3LjY5OC0xMi43OTUgMi41ODEtNi41NTIuODY2LTEzLjI2OS0xLjk2Ny0xNy4wNTItMy40MTMtNC42Ni03LjY2LTYuMDk0LTkuMzI4LTYuNDk1LS4yNDktLjA3NC02LjA4NS0xLjgyNy0xMS4yNC43NTQtMi4yMiAxLjA2NC01LjA5IDMuNDU2LTYuNjI4IDcuMDUtMS42NiAzLjcxMi0xLjIzIDguNDg0IDEuMDIgMTEuNjU1IDIuMjYzIDMuMzk5IDYuNTMyIDUuMzIzIDEwLjIwMyA0LjY3IDMuNjU1LS41NzUgNi4xMjctMy4yOTQgNi43MTMtNS42ODMuNjg5LTIuNTY4LS4zNjgtNC43MzktMS4xOTMtNS41Mi0xLjQwMS0xLjM4My0yLjYxMS0xLjQwNi0yLjY2My0xLjQxYTUgNSAwIDAgMC0xLjAyNi4wNTRjLS42NTQuMjQtMS41ODIuNzY0LTEuODU0IDEuMzE2LS4wNDguMDktLjE3LjM0LjA2NC45NjMuNTYzIDEuNDc3LS4xOTcgMy4xMy0xLjY5NCAzLjY4NS0xLjQ5LjU1OC0zLjE2Ny0uMTkyLTMuNzMzLTEuNjczLS45MzEtMi40NS0uNDE2LTQuMzY4LjE4LTUuNTQ2IDEuNTU4LTMuMDY2IDUuMTc2LTQuMTc2IDUuNTg1LTQuMjk0LjE4Ny0uMDU3LjM4NS0uMDkxLjU4LS4xMDguMzk4LS4wNiAxLjEtLjE1NSAyLjA1Mi0uMTE4IDIuMTc4LjAyNyA0LjY2IDEuMTY1IDYuNTYzIDMuMDQ2IDIuMjQ2IDIuMTIgNC4wMDYgNi4zOCAyLjc1OCAxMS4wMTEtMS4yMTcgNC45NTctNS44OTUgOS4wNTYtMTEuMzUzIDkuOTE0LTUuODcgMS4wNDctMTIuNDgyLTEuODQ3LTE1Ljk3My03LjA5NC0zLjM2NS00LjcyOC00LjAwOS0xMS42NDctMS41NDgtMTcuMTM2IDIuNy02LjMyIDcuODU5LTkuMTggOS4zNzMtOS45MDcgNy4yNzktMy42MzUgMTUuMDU5LTEuMjM1IDE1LjM4Ni0xLjEzNCAyLjA5My40OTQgNy45MTMgMi40NDMgMTIuNDM3IDguNjI1IDMuNDc3IDQuNjUgNi4yOTcgMTMuMzM3IDIuNzE0IDIyLjQzLTMuMjczIDguOTYyLTEyLjU5IDE1Ljc3LTIyLjYzOCAxNi40OTMtLjc4OC4wNzQtMS41ODUuMTE1LTIuMzguMTE1LTkuNzUgMC0xOS42MjctNS42Mi0yNC41OC0xNC4yNSAwIDAtLjgyMS0xLjQxOC0xLjE4My0yLjE1M3oiIHN0cm9rZT0iIzY5MDBiNCIgc3Ryb2tlLXdpZHRoPSIxMCIvPjxwYXRoIGQ9Ik02LjE4MiA1MS4yMDFjLTUuMTM0LTguNDItNS41NTMtMTkuNjU3LTEuMDMtMjguNTY1QzkuODQgMTMuMDYyIDE3LjczIDguODE4IDIwLjkgNy40MzhhMzEuNCAzMS40IDAgMCAxIDYuMDc2LTIuMDgzbDIuMTItLjQ1N2MxLjU1OC0uMzIgMy4xMS42NDYgMy40NDcgMi4xODcuMzQgMS41NDUtLjY1MSAzLjA2Ni0yLjIxNiAzLjQwMmwtMi4wNjMuNDQ1YTI1LjQgMjUuNCAwIDAgMC00Ljk2IDEuNzE2Yy0yLjA4My45MDItOC45NzQgNC4zOTItMTIuOTQ5IDEyLjUxNS0zLjAxNyA1LjkzMy0zLjkxNyAxNS4zNjYuODMyIDIzLjE1IDQuMzAzIDcuNDk4IDEzLjExMiAxMi4wOTUgMjEuNDY4IDExLjMyMSA3LjkwMy0uNTY4IDE1LjE1NS01LjgyOCAxNy42OTgtMTIuNzk1IDIuNTgxLTYuNTUyLjg2Ni0xMy4yNjktMS45NjctMTcuMDUyLTMuNDEzLTQuNjYtNy42Ni02LjA5NC05LjMyOC02LjQ5NS0uMjQ5LS4wNzQtNi4wODUtMS44MjctMTEuMjQuNzU0LTIuMjIgMS4wNjQtNS4wOSAzLjQ1Ni02LjYyOCA3LjA1LTEuNjYgMy43MTItMS4yMyA4LjQ4NCAxLjAyIDExLjY1NSAyLjI2MyAzLjM5OSA2LjUzMiA1LjMyMyAxMC4yMDMgNC42NyAzLjY1NS0uNTc1IDYuMTI3LTMuMjk0IDYuNzEzLTUuNjgzLjY4OS0yLjU2OC0uMzY4LTQuNzM5LTEuMTkzLTUuNTItMS40MDEtMS4zODMtMi42MTEtMS40MDYtMi42NjMtMS40MWE1IDUgMCAwIDAtMS4wMjYuMDU0Yy0uNjU0LjI0LTEuNTgyLjc2NC0xLjg1NCAxLjMxNi0uMDQ4LjA5LS4xNy4zNC4wNjQuOTYzLjU2MyAxLjQ3Ny0uMTk3IDMuMTMtMS42OTQgMy42ODUtMS40OS41NTgtMy4xNjctLjE5Mi0zLjczMy0xLjY3My0uOTMxLTIuNDUtLjQxNi00LjM2OC4xOC01LjU0NiAxLjU1OC0zLjA2NiA1LjE3Ni00LjE3NiA1LjU4NS00LjI5NC4xODctLjA1Ny4zODUtLjA5MS41OC0uMTA4LjM5OC0uMDYgMS4xLS4xNTUgMi4wNTItLjExOCAyLjE3OC4wMjcgNC42NiAxLjE2NSA2LjU2MyAzLjA0NiAyLjI0NiAyLjEyIDQuMDA2IDYuMzggMi43NTggMTEuMDExLTEuMjE3IDQuOTU3LTUuODk1IDkuMDU2LTExLjM1MyA5LjkxNC01Ljg3IDEuMDQ3LTEyLjQ4Mi0xLjg0Ny0xNS45NzMtNy4wOTQtMy4zNjUtNC43MjgtNC4wMDktMTEuNjQ3LTEuNTQ4LTE3LjEzNiAyLjctNi4zMiA3Ljg1OS05LjE4IDkuMzczLTkuOTA3IDcuMjc5LTMuNjM1IDE1LjA1OS0xLjIzNSAxNS4zODYtMS4xMzQgMi4wOTMuNDk0IDcuOTEzIDIuNDQzIDEyLjQzNyA4LjYyNSAzLjQ3NyA0LjY1IDYuMjk3IDEzLjMzNyAyLjcxNCAyMi40My0zLjI3MyA4Ljk2Mi0xMi41OSAxNS43Ny0yMi42MzggMTYuNDkzLS43ODguMDc0LTEuNTg1LjExNS0yLjM4LjExNS05Ljc1IDAtMTkuNjI3LTUuNjItMjQuNTgtMTQuMjUiLz48L2c+PC9zdmc+"; + + let laidImgContain = ""; + const vm = Scratch.vm; + const fontMenu = [ + "Sans Serif", "Serif", "Handwriting", + "Marker", "Curly", "Pixel", "Scratch" + ]; + + const xmlEscape = function (unsafe) { + return Scratch.Cast.toString(unsafe).replace(/[<>&'"]/g, c => { + switch (c) { + case "<": return "<"; + case ">": return ">"; + case "&": return "&"; + case "'": return "'"; + case "\"": return """; + } + }); + }; + + class BetterInputSP { + constructor() { + this.activeOverlays = []; this.askBoxPromises = []; + this.isDropdownOpen = false; + this.userInput = " "; this.defaultValue = ""; + this.textBoxX = 0; this.textBoxY = 0; + this.askBoxInfo = [0, 1]; this.appendTarget = ["window", false]; + this.forceInput = "Disabled"; + this.overlayInput = null; + this.uiOrder = ["question", "input", "buttons"]; + + this.optionList = ["Option 1", "Option 2", "Option 3"]; + this.sliderInfo = [0, 100, 50]; + this.Timeout = 0; + + this.inputType = "Enabled"; + this.fontSize = "14px"; this.fontFamily = "Sans Serif"; this.textAlign = "left"; + // overlay + Image, input, dropdown button + this.mainUIinfo = { + // Border Radius + dimensions: ["auto", "auto"], + overlayRad: 5, + inputRad: 4, + dropBtnRad: 5, + // Border Information + overlayBord: "1px none #000000", + inputBord: "1px solid #000000", + dropBtnBord: "1px none #000000", + // Text Padding + overlayPad: "15px", + inputPad: "5px", + dropBtnPad: "5px 10px", + // Text Shadow + overlayTxtShad: "none", + inputTxtShad: "none", + dropBtnTxtShad: "none", + // Outline: Color + Thickness + overlayOutline: ["", 0], + inputOutline: ["", 0], + dropBtnOutline: ["", 0] + }; + this.DropdownText = "Dropdown"; + this.lastPressBtn = ""; + this.buttonJSON = { + "Submit": { + color: "#0074D9", textColor: "#ffffff", + name: "Submit", image: "", imgScale: 100, + borderRadius: 5, border: "1px none #000000", + padding: "5px 10px", dropShadow: "none", outline: ["", 0] + }, + "Cancel": { + color: "#d9534f", textColor: "#ffffff", + name: "Cancel", image: "", imgScale: 100, + borderRadius: 5, border: "1px none #000000", + padding: "5px 10px", dropShadow: "none", outline: ["", 0] + }, + }; + + this.questionColor = "#000000"; this.inputColor = "#000000"; + this.textBoxColor = ["#ffffff"]; this.inputFieldColor = "#a5aec3"; + this.dropdwnBtnColor = ["#5f5f5f", "#ffffff"]; + this.overlayImage = [" ", " ", " "]; + + this.Blur = 0; this.Brightness = 0; this.Opacity = 100; + this.Invert = 0; this.Saturation = 100; this.Hue = 0; + this.Sepia = 0; this.Contrast = 100; this.Scale = 100; + this.SkewX = 0; this.SkewY = 0; this.Rotation = 90; + this.imgScale = [100, 100, 100]; + this.shadowEnabled = true; + this.shadowS = [0, 0, 5, "#000000"]; + } + + getInfo() { + return { + id: "BetterInputSP", + name: "Better Input", + color1: "#9400ff", + color2: "#7800cd", + color3: "#6900b3", + menuIconURI, + blockIconURI, + blocks: [ + { + opcode: "askAndWait", + blockType: Scratch.BlockType.COMMAND, + text: "ask [question] and wait", + arguments: { + question: { type: Scratch.ArgumentType.STRING, defaultValue: "What is your name?" } + }, + }, + { + opcode: "askAndWaitForInput", + blockType: Scratch.BlockType.REPORTER, + text: "ask [question] and wait", + arguments: { + question: { type: Scratch.ArgumentType.STRING, defaultValue: "What is your name?" } + }, + }, + { + opcode: "getUserInput", blockType: Scratch.BlockType.REPORTER, + text: "user input" + }, + { + opcode: "setDefaultV", + blockType: Scratch.BlockType.COMMAND, + text: "set default value to [defaultV]", + arguments: { + defaultV: { type: Scratch.ArgumentType.STRING, defaultValue: "My Name Is..." } + }, + }, + { + opcode: "removeAskBoxes", blockType: Scratch.BlockType.COMMAND, + text: "remove all ask boxes" + }, + { + opcode: "resetInput", blockType: Scratch.BlockType.COMMAND, + text: "reset user input" + }, + { blockType: Scratch.BlockType.LABEL, text: "Formatting" }, + { + opcode: "setFontSize", + blockType: Scratch.BlockType.COMMAND, + text: "set font size to [SIZE]", + blockIconURI: formatIcon, + arguments: { + SIZE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 14 } + }, + }, + { + opcode: "setTextAlignment", + blockType: Scratch.BlockType.COMMAND, + text: "set alignment to [ALIGNMENT]", + blockIconURI: formatIcon, + arguments: { + ALIGNMENT: { type: Scratch.ArgumentType.STRING, menu: "alignmentMenu" } + }, + }, + { + opcode: "setFontFamily", + blockType: Scratch.BlockType.COMMAND, + text: "set font to [FONT]", + blockIconURI: formatIcon, + arguments: { + FONT: { type: Scratch.ArgumentType.STRING, menu: "fontMenu" } + }, + }, + "---", + { + opcode: "setInputType", + blockType: Scratch.BlockType.COMMAND, + text: "set input type to [ACTION]", + blockIconURI: formatIcon, + arguments: { + ACTION: { type: Scratch.ArgumentType.STRING, menu: "inputActionMenu" } + }, + }, + { + opcode: "setDropdown", + blockType: Scratch.BlockType.COMMAND, + text: "set dropdown options to array: [DROPDOWN]", + blockIconURI: formatIcon, + arguments: { + DROPDOWN: { type: Scratch.ArgumentType.STRING, defaultValue: "[\"Option 1\", \"Option 2\", \"Option 3\"]" } + }, + }, + { + opcode: "setSlider", + blockType: Scratch.BlockType.COMMAND, + text: "set slider to min: [MIN] max: [MAX] default: [DEFAULT]", + blockIconURI: formatIcon, + arguments: { + MIN: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + MAX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + DEFAULT: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + }, + }, + { blockType: Scratch.BlockType.LABEL, text: "Buttons" }, + { + opcode: "setButton", + blockType: Scratch.BlockType.COMMAND, + text: "[BUTTON] button named [NAME]", + blockIconURI: formatIcon, + arguments: { + BUTTON: { type: Scratch.ArgumentType.STRING, menu: "buttonType" }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "Submit" } + }, + }, + { + opcode: "deleteAllButtons", blockType: Scratch.BlockType.COMMAND, + text: "remove all buttons", blockIconURI: formatIcon + }, + { + opcode: "setButtonText", + blockType: Scratch.BlockType.COMMAND, + text: "set [BUTTON_MENU] button name to [TEXT]", + blockIconURI: formatIcon, + arguments: { + BUTTON_MENU: { type: Scratch.ArgumentType.STRING, menu: "buttonMenu" }, + TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: "my dropdown" } + }, + }, + { + opcode: "lastButton", blockType: Scratch.BlockType.REPORTER, + text: "last pressed button", blockIconURI: formatIcon + }, + { blockType: Scratch.BlockType.LABEL, text: "Positioning" }, + { + opcode: "setPrePosition", + blockType: Scratch.BlockType.COMMAND, + text: "preset textbox position to x: [X] y: [Y]", + blockIconURI: formatIcon, + arguments: { + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } + }, + }, + { + opcode: "setPosition", + blockType: Scratch.BlockType.COMMAND, + text: "set textbox position to x: [X] y: [Y]", + blockIconURI: formatIcon, + arguments: { + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } + }, + }, + { + opcode: "changePosition", + blockType: Scratch.BlockType.COMMAND, + text: "change textbox position by x: [X] y: [Y]", + blockIconURI: formatIcon, + arguments: { + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } + }, + }, + { + opcode: "getXpos", blockType: Scratch.BlockType.REPORTER, + blockIconURI: formatIcon, text: "x position" + }, + { + opcode: "getYpos", blockType: Scratch.BlockType.REPORTER, + blockIconURI: formatIcon, text: "y position" + }, + { + opcode: "setDirection", + blockType: Scratch.BlockType.COMMAND, + text: "set direction to [ROTATE]", + blockIconURI: formatIcon, + arguments: { + ROTATE: { type: Scratch.ArgumentType.ANGLE, defaultValue: 90 } + }, + }, + { + opcode: "changeDirection", + blockType: Scratch.BlockType.COMMAND, + text: "change direction by [ROTATE]", + blockIconURI: formatIcon, + arguments: { + ROTATE: { type: Scratch.ArgumentType.ANGLE, defaultValue: 15 } + }, + }, + { + opcode: "reportDirection", blockType: Scratch.BlockType.REPORTER, + text: "direction", blockIconURI: formatIcon + }, + { blockType: Scratch.BlockType.LABEL, text: "Visual Settings" }, + { + opcode: "setColorSettings", + blockType: Scratch.BlockType.COMMAND, + text: "set [COLOR_TYPE] color to [COLOR]", + blockIconURI: colorIcon, + arguments: { + COLOR_TYPE: { type: Scratch.ArgumentType.STRING, menu: "colorSettingsMenu" }, + COLOR: { type: Scratch.ArgumentType.COLOR } + }, + }, + "---", + { + opcode: "setImage", + blockType: Scratch.BlockType.COMMAND, + text: "set [ELEMENT] image to [IMAGE]", + blockIconURI: colorIcon, + arguments: { + ELEMENT: { type: Scratch.ArgumentType.STRING, menu: "elementMenu" }, + IMAGE: { type: Scratch.ArgumentType.STRING, defaultValue: "input-url-here" } + }, + }, + { + opcode: "scaleImage", + blockType: Scratch.BlockType.COMMAND, + text: "scale [ELEMENT] image to [SCALE]%", + blockIconURI: colorIcon, + arguments: { + ELEMENT: { type: Scratch.ArgumentType.STRING, menu: "elementMenu" }, + SCALE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } + }, + }, + "---", + { + opcode: "enableShadow", + blockType: Scratch.BlockType.COMMAND, + text: "set box shadow to [ACTION]", + blockIconURI: colorIcon, + arguments: { + ACTION: { type: Scratch.ArgumentType.STRING, menu: "buttonActionMenu" } + }, + }, + { + opcode: "setShadow", + blockType: Scratch.BlockType.COMMAND, + text: "set box shadow [SHADOW] to [AMT]", + blockIconURI: colorIcon, + arguments: { + SHADOW: { type: Scratch.ArgumentType.STRING, menu: "shadowStuff" }, + AMT: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + }, + }, + { + opcode: "setDropShadow", + blockType: Scratch.BlockType.COMMAND, + text: "set [ELEMENT] shadow to x [x] y [y] z [z] color [COLOR]", + blockIconURI: colorIcon, + arguments: { + ELEMENT: { type: Scratch.ArgumentType.STRING, menu: "textsMenu" }, + x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + z: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 }, + COLOR: { type: Scratch.ArgumentType.COLOR } + }, + }, + { + opcode: "setOutline", + blockType: Scratch.BlockType.COMMAND, + text: "set [ELEMENT] outline to [COLOR] thickness [THICK]", + blockIconURI: colorIcon, + arguments: { + ELEMENT: { type: Scratch.ArgumentType.STRING, menu: "textsMenu" }, + COLOR: { type: Scratch.ArgumentType.COLOR }, + THICK: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + }, + }, + "---", + { + opcode: "setBorder", + blockType: Scratch.BlockType.COMMAND, + text: "set [ELEMENT] border to [TYPE] color [COLOR] width [WIDTH]", + blockIconURI: colorIcon, + arguments: { + ELEMENT: { type: Scratch.ArgumentType.STRING, menu: "elementMenu" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "borderTypes" }, + COLOR: { type: Scratch.ArgumentType.COLOR }, + WIDTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + }, + }, + { + opcode: "setBorderRadius", + blockType: Scratch.BlockType.COMMAND, + text: "set [ELEMENT] border radius to [VALUE]", + blockIconURI: colorIcon, + arguments: { + ELEMENT: { type: Scratch.ArgumentType.STRING, menu: "elementMenu" }, + VALUE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + }, + }, + "---", + { + opcode: "setPadding", + blockType: Scratch.BlockType.COMMAND, + text: "set [ELEMENT] padding to T: [N1] B: [N3] L: [N4] R: [N2]", + blockIconURI: colorIcon, + arguments: { + ELEMENT: { type: Scratch.ArgumentType.STRING, menu: "elementMenu" }, + N1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + N2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + N3: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + N4: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + }, + }, + { + opcode: "setDimension", + blockType: Scratch.BlockType.COMMAND, + text: "set Textbox width [W] height [H]", + blockIconURI: colorIcon, + arguments: { + W: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + H: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } + }, + }, + { blockType: Scratch.BlockType.LABEL, text: "Effects" }, + { + opcode: "resetEffect", + blockType: Scratch.BlockType.COMMAND, + text: "reset effects", + blockIconURI: effectIcon + }, + { + opcode: "setEffect", + blockType: Scratch.BlockType.COMMAND, + text: "set effect [EFFECT] to [AMT]", + blockIconURI: effectIcon, + arguments: { + EFFECT: { type: Scratch.ArgumentType.STRING, menu: "effectMenu" }, + AMT: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + }, + }, + { + opcode: "changeEffect", + blockType: Scratch.BlockType.COMMAND, + text: "change effect [EFFECT] by [AMT]", + blockIconURI: effectIcon, + arguments: { + EFFECT: { type: Scratch.ArgumentType.STRING, menu: "effectMenu" }, + AMT: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + }, + }, + { + opcode: "showEffect", + blockType: Scratch.BlockType.REPORTER, + text: "effect [EFFECT]", + blockIconURI: effectIcon, + arguments: { + EFFECT: { type: Scratch.ArgumentType.STRING, menu: "effectMenu" } + }, + }, + "---", + { + opcode: "setTimeout", + blockType: Scratch.BlockType.COMMAND, + text: "when submitted delete textbox after [TIME] secs", + blockIconURI: effectIcon, + arguments: { + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + }, + }, + { + opcode: "reportTimeout", + blockType: Scratch.BlockType.REPORTER, + text: "current textbox timeout", + blockIconURI: effectIcon + }, + { blockType: Scratch.BlockType.LABEL, text: "Operations" }, + { + opcode: "setUI", + blockType: Scratch.BlockType.COMMAND, + text: "set UI order to [ARRAY]", + arguments: { + ARRAY: { type: Scratch.ArgumentType.STRING, defaultValue: "[\"question\", \"input\", \"buttons\"]" } + }, + }, + { + opcode: "getUIOrder", + blockType: Scratch.BlockType.REPORTER, + text: "UI order" + }, + "---", + { + opcode: "setAppend", + blockType: Scratch.BlockType.COMMAND, + text: "append next textbox to [TARGET]", + arguments: { + TARGET: { type: Scratch.ArgumentType.STRING, menu: "appendMenu" } + }, + }, + { + opcode: "setFocus", + blockType: Scratch.BlockType.COMMAND, + text: "toggle focus mode to [TYPE]", + arguments: { + TYPE: { type: Scratch.ArgumentType.STRING, menu: "buttonActionMenu" } + }, + }, + "---", + { + opcode: "isWaitingInput", + blockType: Scratch.BlockType.BOOLEAN, + text: "is waiting?" + }, + { + opcode: "isDropdown", + blockType: Scratch.BlockType.BOOLEAN, + text: "is dropdown open?" + }, + { + opcode: "setSubmitEvent", + blockType: Scratch.BlockType.COMMAND, + text: "set force input to [ENTER]", + arguments: { + ENTER: { type: Scratch.ArgumentType.STRING, menu: "enterMenu" } + }, + }, + { + opcode: "setMaxBoxCount", + blockType: Scratch.BlockType.COMMAND, + text: "set max box count to: [MAX]", + arguments: { + MAX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 } + }, + }, + { + opcode: "getBoxInfo", + blockType: Scratch.BlockType.REPORTER, + text: "textbox [INFO]", + arguments: { + INFO: { type: Scratch.ArgumentType.STRING, menu: "boxInfo" } + }, + } + ], + menus: { + fontMenu: { acceptReporters: true, items: "allFonts" }, + buttonMenu: { + acceptReporters: true, + items: this.allButtons(["Dropdown"], false), + }, + elementMenu: { + acceptReporters: true, + items: this.allButtons(["Textbox", "Input Box", "Dropdown Button"], false), + }, + colorSettingsMenu: { + acceptReporters: true, + items: this.allButtons([ + "Textbox", "Question Text", "Textbox Shadow", + "Input Text", "Input Box", + "Dropdown Button", "Dropdown Text" + ], true), + }, + textsMenu: { + acceptReporters: true, + items: this.allButtons(["Question Text", "Input Text", "Dropdown Text"], true, true), + }, + appendMenu: ["window", "canvas"], + buttonType: { acceptReporters: true, items: ["add", "remove"] }, + buttonActionMenu: { acceptReporters: true, items: ["Enabled", "Disabled"] }, + alignmentMenu: { acceptReporters: true, items: ["left", "right", "center"] }, + shadowStuff: { acceptReporters: true, items: ["Size", "X", "Y"] }, + boxInfo: { + acceptReporters: true, + items: ["count", "limit", "button count", "button names"], + }, + inputActionMenu: { + acceptReporters: true, + items: [ + "None", "Text", "Text Area", "Password", "Number", "Color", + "Dropdown", "Single Dropdown", "Multi-Select Dropdown", + "Horizontal Slider", "Vertical Slider" + ], + }, + effectMenu: { + acceptReporters: true, + items: [ + "Blur", "Brightness", "Opacity", "Invert", + "Saturation", "Hue", "Sepia", "Contrast", + "Scale", "SkewX", "SkewY", + ], + }, + enterMenu: { + acceptReporters: true, + items: ["Disabled", "Enter Key", "Shift + Enter Key"], + }, + borderTypes: { + acceptReporters: true, + items: [ + "none", "solid", "dotted", "dashed", + "double", "groove", "ridge", "inset", "outset" + ], + } + }, + }; + } + + allFonts() { + const custFonts = vm.runtime.fontManager ? vm.runtime.fontManager.getFonts().map((i) => ({ text: i.name, value: i.family })) : []; + return [ ...fontMenu, ...custFonts ]; + } + + allButtons(array, enableTxt, justTxt) { + let customBtn = Object.keys(this.buttonJSON); + if (justTxt) customBtn = customBtn.map(btn => btn + " Text"); + else if (enableTxt) customBtn.forEach((btn) => { customBtn.push(btn + " Text") }); + return [ ...array, ...customBtn ]; + } + + updateOverlayPos(overlay) { + if (this.Rotation > 359) this.Rotation = 0; + else if (this.Rotation < 1) this.Rotation = 360; + if (this.textBoxX !== null && this.textBoxY !== null) { + if (this.appendTarget[0] === "window") { + overlay.style.left = `${50 + this.textBoxX}%`; + overlay.style.top = `${50 + this.textBoxY}%`; + } + overlay.style.transform = ` + translate${this.appendTarget[0] === "window" ? "(-50%, -50%)" : `(${-50 + this.textBoxX}%, ${-50 + this.textBoxY}%)` } + SkewX(${this.SkewX}deg) SkewY(${this.SkewY}deg) + rotate(${this.Rotation - 90}deg) scale(${this.Scale / 100}) + `; + } else { + overlay.style.left = "50%"; + overlay.style.top = "50%"; + } + } + updateOverlay(overlay) { + const newOpacity = this.Opacity / 100; + const newBrightness = this.Brightness + 100; + overlay.style.backgroundImage = ""; + overlay.style[this.textBoxColor[0].includes("gradient") ? "backgroundImage" : "backgroundColor"] = this.textBoxColor[0]; + overlay.style.boxShadow = this.shadowEnabled ? `${this.shadowS[0]}px ${this.shadowS[1]}px ${this.shadowS[2]}px ${this.shadowS[3]}` : "none"; + overlay.style.transform = ` + translate${this.appendTarget[0] === "window" ? "(-50%, -50%)" : `(${-50 + this.textBoxX}%, ${-50 + this.textBoxY}%)` } + SkewX(${this.SkewX}deg) SkewY(${this.SkewY}deg) + rotate(${this.Rotation - 90}deg) scale(${this.Scale / 100}) + `; + overlay.style.filter = ` + blur(${this.Blur}px) brightness(${newBrightness}%) + invert(${this.Invert}%) saturate(${this.Saturation}%) + hue-rotate(${this.Hue}deg) sepia(${this.Sepia}%) + contrast(${this.Contrast}%) + `; + overlay.style.opacity = newOpacity; + overlay.style.border = this.mainUIinfo.overlayBord; + overlay.style.padding = this.mainUIinfo.overlayPad; + overlay.style.fontFamily = this.fontFamily; + overlay.style.textAlign = this.textAlign; + overlay.style.borderRadius = `${this.mainUIinfo.overlayRad}px`; + overlay.style.width = this.mainUIinfo.dimensions[0]; + overlay.style.height = this.mainUIinfo.dimensions[1]; + laidImgContain.style.borderRadius = `${this.mainUIinfo.overlayRad}px`; + laidImgContain.style.background = ""; + this.setImageStyles(laidImgContain, this.overlayImage[0], this.imgScale[0]); + this.updateButtonImages(overlay); + } + updateButtonImages(overlay) { + let text = overlay.querySelector(".question"); + if (text) { + text.style.color = this.questionColor; + text.style.textShadow = this.mainUIinfo.overlayTxtShad; + this.tryOutline(text, this.mainUIinfo.overlayOutline[0], this.mainUIinfo.overlayOutline[1]); + } + const inputField = overlay.querySelector(this.inputType.includes("Single") ? "select" : this.inputType === "Text Area" ? "textarea" : "input"); + if (inputField) { + const inpWidth = parseInt(this.mainUIinfo.dimensions[0]); + inputField.style.width = this.inputType === "Color" || this.inputType.includes("Single") ? "100%" : + this.inputType === "Horizontal Slider" ? "95%" : isNaN(inpWidth) || this.inputType.includes("Dropdown") ? "auto" : `${inpWidth - 10}px`; + inputField.style.background = ""; + inputField.style.fontFamily = this.fontFamily; + inputField.style[this.inputFieldColor.includes("gradient") ? "backgroundImage" : "backgroundColor"] = this.inputFieldColor; + inputField.style.color = this.inputColor; inputField.style.accentColor = this.inputFieldColor; + inputField.style.textShadow = this.mainUIinfo.inputTxtShad; + this.tryOutline(inputField, this.mainUIinfo.inputOutline[0], this.mainUIinfo.inputOutline[1]); + inputField.style.border = this.mainUIinfo.inputBord; + inputField.style.borderRadius = `${this.mainUIinfo.inputRad}px`; + inputField.style.padding = this.mainUIinfo.inputPad; + this.setImageStyles(inputField, this.overlayImage[1], this.imgScale[1]); + } + + const dropBtn = overlay.querySelector("button.dropbtn"); + if (dropBtn) { + dropBtn.style.backgroundImage = ""; + dropBtn.style.fontFamily = this.fontFamily; + dropBtn.style.color = this.dropdwnBtnColor[1]; + dropBtn.style.borderRadius = `${this.mainUIinfo.dropBtnRad}px`; + dropBtn.style.border = this.mainUIinfo.dropBtnBord; + dropBtn.style.padding = this.mainUIinfo.dropBtnPad; + dropBtn.style.textShadow = this.mainUIinfo.dropBtnTxtShad; + this.tryOutline(dropBtn, this.mainUIinfo.dropBtnOutline[0], this.mainUIinfo.dropBtnOutline[1]); + dropBtn.style[this.dropdwnBtnColor[0].includes("gradient") ? "backgroundImage" : "backgroundColor"] = this.dropdwnBtnColor[0]; + this.setImageStyles(dropBtn, this.overlayImage[2], this.imgScale[2]); + } + const btnContain = overlay.querySelector(".button-container"); + if (btnContain) { + const buttons = btnContain.querySelectorAll("button"); + buttons.forEach((button, index) => { + const buttonName = Object.keys(this.buttonJSON)[index]; + const buttonInfo = this.buttonJSON[buttonName]; + if (buttonInfo) { + button.style.color = buttonInfo.textColor; + button.style.fontFamily = this.fontFamily; + button.style.fontSize = this.fontSize; + button.style.borderRadius = `${buttonInfo.borderRadius}px`; + button.style.border = buttonInfo.border; + button.style.padding = buttonInfo.padding; + button.style.textShadow = buttonInfo.dropShadow; + this.tryOutline(button, buttonInfo.outline[0], buttonInfo.outline[1]); + button.style.background = ""; + button.style[buttonInfo.color.includes("gradient") ? "backgroundImage" : "background"] = buttonInfo.color; + this.setImageStyles(button, buttonInfo.image, buttonInfo.imgScale); + } + }); + } + } + tryOutline(element, color, thick) { + element.style.webkitTextStrokeColor = color; + element.style.webkitTextStrokeWidth = `${thick}px`; + //multi-platform support cuz we cant have nice things + element.style.textStrokeColor = color; + element.style.textStrokeWidth = `${thick}px`; + element.style.mozTextStrokeColor = color; + element.style.mozTextStrokeWidth = `${thick}px`; + } + + setImageStyles(element, url, scale) { + if (Scratch.Cast.toString(url).length > 5) { + Scratch.canFetch(encodeURI(url)).then((canFetch) => { + if (canFetch) { + element.style.background = `url(${encodeURI(url)})`; + element.style.backgroundSize = `${scale}%`; + } else { console.warn("Cannot fetch content from the URL") } + }); + } + } + + showEffect(args) { return this[args.EFFECT] } + + setEffect(args) { + this[args.EFFECT] = args.AMT; + this.activeOverlays.forEach((overlay) => { this.updateOverlay(overlay) }); + } + + changeEffect(args) { + const effect = args.EFFECT; + this[effect] = this[effect] + args.AMT; + this.activeOverlays.forEach((overlay) => { this.updateOverlay(overlay) }); + } + + resetEffect() { + this.Blur = 0; this.Brightness = 0; this.Opacity = 100; this.Invert = 0; + this.Saturation = 100; this.Hue = 0; this.Sepia = 0; this.Contrast = 100; + this.Scale = 100; this.SkewX = 0; this.SkewY = 0; + this.activeOverlays.forEach((overlay) => { this.updateOverlay(overlay) }); + } + + setColorSettings(args) { + const colorType = args.COLOR_TYPE; + const colorValue = args.COLOR; + const colorTypeMap = { + "Question Text": () => this.questionColor = colorValue, + "Input Text": () => this.inputColor = colorValue, + "Textbox": () => { this.textBoxColor[0] = colorValue; this.overlayImage[0] = " "; }, + "Textbox Shadow": () => { this.shadowS[3] = colorValue }, + "Input Box": () => { this.inputFieldColor = colorValue; this.overlayImage[1] = " "; }, + "Dropdown Button": () => { this.dropdwnBtnColor[0] = colorValue; this.overlayImage[2] = " "; }, + "Dropdown Text": () => this.dropdwnBtnColor[1] = colorValue, + }; + const buttonInfo = this.buttonJSON[colorType] || this.buttonJSON[colorType.replace(" Text", "")]; + if (buttonInfo) { + if (colorType.includes(" Text")) buttonInfo.textColor = colorValue; + else { + buttonInfo.color = colorValue; + buttonInfo.image = " "; + } + } + const applyColor = colorTypeMap[colorType]; + if (applyColor) applyColor(); + this.activeOverlays.forEach(overlay => this.updateOverlay(overlay)); + } + + findGradientType(menu) { + const colorTypeMap = { + Textbox: { newColorType: "textBoxColor", ind: 0 }, + "Dropdown Button": { newColorType: "dropdwnBtnColor", ind: 2 } + }; + if (colorTypeMap[menu]) { + const { newColorType, ind } = colorTypeMap[menu]; + this.overlayImage[ind] = " "; + return newColorType; + } else if (this.buttonJSON[menu]) { return ["button", menu] } + return menu; + } + + callStyling(element, value, type, elements) { + const elementID = elements[element]; + if (elementID !== undefined) this.mainUIinfo[elementID] = value; + else if (this.buttonJSON[element]) this.buttonJSON[element][type] = value; + this.activeOverlays.forEach(overlay => this.updateOverlay(overlay)); + } + + setBorder(args) { + const width = Scratch.Cast.toNumber(args.WIDTH); + const string = `${width}px ${args.TYPE} ${args.COLOR}`; + this.callStyling( + args.ELEMENT, string, "border", + { Textbox: "overlayBord", "Input Box": "inputBord", "Dropdown Button": "dropBtnBord" } + ); + } + + setBorderRadius(args) { + this.callStyling( + args.ELEMENT, Math.max(args.VALUE, 0), "borderRadius", + { Textbox: "overlayRad", "Input Box": "inputRad", "Dropdown Button": "dropBtnRad" } + ); + } + + setPadding(args) { + const casted = [ + Scratch.Cast.toNumber(args.N1), Scratch.Cast.toNumber(args.N2), + Scratch.Cast.toNumber(args.N3), Scratch.Cast.toNumber(args.N4) + ]; + let pad = `${casted[0]}px ${casted[1]}px ${casted[2]}px ${casted[3]}px`; + this.callStyling( + args.ELEMENT, pad, "padding", + { Textbox: "overlayPad", "Input Box": "inputPad", "Dropdown Button": "dropBtnPad" } + ); + } + + setDropShadow(args) { + const casted = [ + Scratch.Cast.toNumber(args.x), Scratch.Cast.toNumber(args.y), Scratch.Cast.toNumber(args.z) + ]; + let shadow = args.z === 0 ? "none" : `${casted[0]}px ${casted[1] * -1}px ${casted[2]}px ${args.COLOR}`; + this.callStyling( + args.ELEMENT.slice(0, -5), shadow, "dropShadow", + { "Question": "overlayTxtShad", "Input": "inputTxtShad", "Dropdown": "dropBtnTxtShad" } + ); + } + + setOutline(args) { + const thick = Scratch.Cast.toNumber(args.THICK); + this.callStyling( + args.ELEMENT.slice(0, -5), [args.COLOR, thick], "outline", + { "Question": "overlayOutline", "Input": "inputOutline", "Dropdown": "dropBtnOutline" } + ); + } + + setShadow(args) { + const shadowMap = { Size: 2, X: 0, Y: 1 }; + const propertyIndex = shadowMap[args.SHADOW]; + if (propertyIndex !== undefined) this.shadowS[propertyIndex] = args.AMT; + this.activeOverlays.forEach(overlay => this.updateOverlay(overlay)); + } + + setImage(args) { + const elementMap = { Textbox: 0, "Input Box": 1, "Dropdown Button": 2 }; + const elementIndex = elementMap[args.ELEMENT]; + if (elementIndex !== undefined) this.overlayImage[elementIndex] = args.IMAGE; + else if (this.buttonJSON[args.ELEMENT]) this.buttonJSON[args.ELEMENT].image = args.IMAGE; + this.activeOverlays.forEach(overlay => this.updateOverlay(overlay)); + } + + scaleImage(args) { + const elementMap = { Textbox: 0, "Input Box": 1, "Dropdown Button": 2 }; + const elementIndex = elementMap[args.ELEMENT]; + if (elementIndex !== undefined) this.imgScale[elementIndex] = args.SCALE; + else if (this.buttonJSON[args.ELEMENT]) this.buttonJSON[args.ELEMENT].imgScale = args.SCALE; + this.activeOverlays.forEach(overlay => this.updateOverlay(overlay)); + } + + setDimension(args) { + const w = `${Scratch.Cast.toNumber(args.W)}px`; + const h = `${Scratch.Cast.toNumber(args.H)}px`; + // Negative numbers result in auto-dimensions + this.mainUIinfo.dimensions = [w.includes("-") ? "auto" : w, h.includes("-") ? "auto" : h]; + this.activeOverlays.forEach(overlay => this.updateOverlay(overlay)); + } + + setDirection(args) { + this.Rotation = Scratch.Cast.toNumber(args.ROTATE); + this.activeOverlays.forEach((overlay) => { this.updateOverlay(overlay) }); + } + + changeDirection(args) { + this.Rotation = this.Rotation + Scratch.Cast.toNumber(args.ROTATE); + this.activeOverlays.forEach((overlay) => { this.updateOverlay(overlay) }); + } + + reportDirection() { return this.Rotation } + + setPrePosition(args) { + this.textBoxX = Scratch.Cast.toNumber(args.X) / (screen.width / 400); + this.textBoxY = Scratch.Cast.toNumber(args.Y) / (screen.height / -300); + } + + setPosition(args) { + this.textBoxX = Scratch.Cast.toNumber(args.X) / (screen.width / 400); + this.textBoxY = Scratch.Cast.toNumber(args.Y) / (screen.height / -300); + this.activeOverlays.forEach((overlay) => { this.updateOverlayPos(overlay) }); + } + + changePosition(args) { + this.textBoxX = this.textBoxX + Scratch.Cast.toNumber(args.X) / (screen.width / 400); + this.textBoxY = this.textBoxY + Scratch.Cast.toNumber(args.Y) / (screen.height / -300); + this.activeOverlays.forEach((overlay) => { this.updateOverlayPos(overlay) }); + } + + getXpos() { return this.textBoxX * (screen.width / 400) } + getYpos() { return this.textBoxY * (screen.height / -300) } + + setFontSize(args) { this.fontSize = args.SIZE + "px" } + + setTextAlignment(args) { + this.textAlign = args.ALIGNMENT; + this.activeOverlays.forEach((overlay) => { this.updateOverlay(overlay) }); + } + + setFontFamily(args) { + this.fontFamily = args.FONT; + this.activeOverlays.forEach((overlay) => { this.updateOverlay(overlay) }); + } + + setSlider(args) { this.sliderInfo = [args.MIN, args.MAX, args.DEFAULT] } + + setInputType(args) { + if (args.ACTION === "Text" || args.ACTION === "None") this.inputType = args.ACTION === "Text" ? "Enabled" : "Disabled"; + else this.inputType = args.ACTION; + } + + enableShadow(args) { this.shadowEnabled = args.ACTION === "Enabled" } + + setButtonText(args) { + const buttonMenu = args.BUTTON_MENU; + const text = args.TEXT; + if (buttonMenu === "Dropdown") this.DropdownText = text; + else if (this.buttonJSON[buttonMenu]) { + this.buttonJSON[buttonMenu].name = text; + vm.extensionManager.refreshBlocks(); + } + } + + setDropdown(args) { + try { + this.optionList = JSON.parse(args.DROPDOWN); + } catch { this.optionList = ["Invalid Array"] } + } + + removeAskBoxes() { + const overlaysToRemove = []; + this.activeOverlays.forEach((overlay) => { + if (overlay) { + if (this.appendTarget[0] === "window" && overlay.parentNode) overlay.parentNode.removeChild(overlay); + else if (overlay.parentNode.parentNode !== document.documentElement) overlay.parentNode.parentNode.removeChild(overlay.parentNode); + overlaysToRemove.push(overlay); + } + if (this.askBoxPromises) { + const index = this.activeOverlays.indexOf(overlay); + if (index !== -1) this.askBoxPromises[index].resolve("removed"); + } + }); + this.askBoxPromises = []; + this.activeOverlays = this.activeOverlays.filter((overlay) => !overlaysToRemove.includes(overlay)); + this.askBoxInfo[0] = 0; + this.isDropdownOpen = false; + const bugged = document.querySelectorAll(`div[class^="SP-ask-box"]`); + bugged.forEach((box) => { box.parentNode.removeChild(box) }); + } + + resetInput() { this.userInput = this.askBoxInfo[1] > 1 ? [] : "" } + + askAndWaitForInput(args) { + if (this.askBoxInfo[0] < this.askBoxInfo[1] ) { + return this.askAndWait(args).then(() => { return this.getUserInput() }); + } + } + + askAndWait(args) { + if (this.askBoxInfo[0] < this.askBoxInfo[1]) { + const question = args.question; + let hasDecreased = false; // for the box counter + const index = this.askBoxInfo[0]; + this.lastPressBtn = ""; + this.askBoxInfo[0]++; + let selectOpts = []; + return new Promise((resolve) => { + this.askBoxPromises.push({ resolve }); + const overlay = document.createElement("div"); + overlay.classList.add("SP-ask-box"); + overlay.style.pointerEvents = "auto"; + overlay.style.position = "fixed"; + overlay.style.fontSize = this.fontSize; + overlay.style.left = this.appendTarget[0] === "window" ? `${50 + this.textBoxX}%` : "0%"; + overlay.style.top = this.appendTarget[0] === "window" ? `${50 + this.textBoxY}%` : "0%"; + + const focusBG = document.createElement("div"); + focusBG.style.cssText = "pointer-events: auto; position: fixed; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9998;"; + focusBG.className = "SP-ask-boxBG"; + focusBG.id = this.appendTarget[0]; + focusBG.style.left = this.appendTarget[0] === "window" ? "0%" : "-50%"; + focusBG.style.top = this.appendTarget[0] === "window" ? "0%" : "-50%"; + + laidImgContain = document.createElement("div"); + laidImgContain.style.width = "100%"; + laidImgContain.style.height = "100%"; + laidImgContain.style.position = "absolute"; + laidImgContain.style.top = 0; + laidImgContain.style.left = 0; + laidImgContain.style.zIndex = "-1"; + if (this.forceInput !== "Disabled") { + const overlayInput = this.forceInput === "Enter Key" ? "Enter" : this.forceInput === "Shift + Enter Key" ? "ShiftEnter" : this.forceInput; + const handleKeydown = (event) => { + if ((overlayInput === "ShiftEnter" && event.shiftKey && event.key === "Enter") || event.key === overlayInput) { + setInpValue(inputField.value); + this.closeOverlay(overlay, hasDecreased); + hasDecreased = true; + resolve(); + } + }; + const observer = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === "childList" && !document.contains(overlay)) { + document.removeEventListener("keydown", handleKeydown); + observer.disconnect(); + } + } + }); + observer.observe(document.body, { childList: true }); + document.addEventListener("keydown", handleKeydown); + } + + const questionText = document.createElement("div"); + questionText.classList.add("question"); + questionText.style.fontSize = this.fontSize; + if (this.uiOrder[0] !== "question") questionText.style.marginTop = "10px"; + if (this.uiOrder[0] === "question") questionText.style.marginBottom = "10px"; + questionText.innerHTML = xmlEscape(question).replace(/\n/g, "
"); + + const inputField = document.createElement(this.inputType === "Text Area" ? "textarea" : "input"); + inputField.style.display = this.inputType ? "block" : "none"; + inputField.style.fontSize = this.fontSize; + inputField.style.margin = "0 auto"; + if (this.inputType !== "Text Area") inputField.type = this.inputType.toLowerCase(); + const setInpValue = (val) => { + inputField.value = val; + if (this.askBoxInfo[1] == 1) this.userInput = inputField.value; + else { + const newInput = [...this.userInput]; + newInput[index] = inputField.value; + this.userInput = newInput; + } + } + inputField.addEventListener("input", () => { setInpValue(inputField.value) }); + const btnContain = document.createElement("div"); + btnContain.classList.add("button-container"); + for (const buttonName in this.buttonJSON) { + const btnInfo = this.buttonJSON[buttonName]; + if (btnInfo.name.includes("")) btnContain.appendChild(document.createElement("br")); + else { + const btn = document.createElement("button"); + if (this.uiOrder[0] !== "buttons") btn.style.marginTop = "10px"; + if (this.uiOrder[2] !== "buttons") btn.style.marginBottom = "10px"; + btn.style.marginRight = "5px"; + btn.style.cursor = "pointer"; + btn.innerHTML = xmlEscape(btnInfo.name).replace(/\n/g, "
"); + btn.style.display = "inline-block"; + btn.addEventListener("click", () => { + this.lastPressBtn = btnInfo.name; + setInpValue(this.inputType === "Disabled" ? btnInfo.name : this.userInput); + this.closeOverlay(overlay, hasDecreased); + hasDecreased = true; + resolve(); + }); + btnContain.appendChild(btn); + } + } + let dropdwnCont, dropdwnBtn, sliderContain, valTxt; + if (this.inputType.includes("Dropdown")) { + const dropdown = document.createElement("div"); + dropdown.className = "dropdown"; + if (this.inputType === "Single Dropdown") { + dropdwnBtn = document.createElement("select"); + this.optionList.forEach((label) => { + let opt = document.createElement("option"); + opt.value = label; opt.text = label; + dropdwnBtn.appendChild(opt); + }); + dropdwnBtn.addEventListener("input", () => { setInpValue(dropdwnBtn.value) }); + dropdwnBtn.value = this.defaultValue || dropdwnBtn.value; + setInpValue(dropdwnBtn.value); + } else { + const isMulti = this.inputType.includes("Multi-Select"); + let defaultOpts = []; + if (isMulti) { + try { + defaultOpts = JSON.parse(this.defaultValue); + selectOpts = defaultOpts; + } catch {} + } + dropdwnBtn = document.createElement("button"); + dropdwnBtn.className = "dropbtn"; + dropdwnBtn.innerHTML = xmlEscape(this.DropdownText).replace(/\n/g, "
"); + dropdwnCont = document.createElement("div"); + dropdwnCont.id = "myDropdown"; + dropdwnCont.className = "dropdown-content"; + dropdwnCont.style.display = "none"; + this.optionList.forEach((label, index) => { + const optTxt = document.createElement("label"); + optTxt.style.color = this.questionColor; + optTxt.textContent = ""; + const optRadio = document.createElement("input"); + optRadio.type = this.inputType === "Dropdown" ? "radio" : "checkbox"; + if (isMulti) optRadio.checked = defaultOpts.indexOf(label) > -1; + else optRadio.checked = label === this.defaultValue; + optRadio.name = "dropdownOptions"; + optRadio.value = index; + optRadio.classList.add("dropdown-radio"); + optRadio.addEventListener("click", () => { + if (isMulti) { + if (selectOpts.includes(label)) selectOpts = selectOpts.filter(item => item !== label); + else selectOpts.push(label); + inputField.value = selectOpts.length > 0 ? JSON.stringify(selectOpts) : ""; + } else { inputField.value = label } + setInpValue(inputField.value) + }); + optTxt.append(optRadio, document.createTextNode(" " + label), document.createElement("br")); + dropdwnCont.appendChild(optTxt); + }); + dropdwnBtn.addEventListener("click", () => { + this.lastPressBtn = this.DropdownText; + dropdwnCont.style.display = this.isDropdownOpen ? "none" : "block"; + this.isDropdownOpen = !this.isDropdownOpen; + }); + setInpValue(this.defaultValue); + } + } else if (this.inputType.includes("Slider")) { + sliderContain = document.createElement("div"); + sliderContain.classList.add("slider-container"); + const slider = document.createElement("input"); + slider.type = "range"; + slider.min = this.sliderInfo[0]; slider.max = this.sliderInfo[1]; slider.value = this.sliderInfo[2]; + if (this.inputType.includes("Vertical")) { + slider.style.writingMode = "vertical-lr"; + slider.style.direction = "rtl"; + } + sliderContain.appendChild(slider); + valTxt = document.createElement("span"); + valTxt.classList.add("slider-value"); + sliderContain.appendChild(valTxt); + valTxt.style.color = this.questionColor; + valTxt.textContent = slider.value; + slider.addEventListener("input", () => { + valTxt.textContent = slider.value; + setInpValue(valTxt.textContent); + }); + setInpValue(valTxt.textContent); + } + for (const item of this.uiOrder) { + switch (item) { + case "question": { overlay.appendChild(questionText); break } + case "input": + if (this.inputType !== "Disabled") { + const createBr = () => { return document.createElement("br") }; + if (this.inputType === "Single Dropdown") overlay.append(dropdwnBtn, createBr()); + else if (this.inputType.includes("Dropdown")) overlay.append(dropdwnBtn, dropdwnCont, createBr()); + else if (this.inputType.includes("Slider")) overlay.append(sliderContain, valTxt, createBr()); + else { + setInpValue(this.defaultValue); + overlay.appendChild(inputField); + } + } + break; + case "buttons": { overlay.appendChild(btnContain); break } + } + } + overlay.appendChild(laidImgContain); + if (this.appendTarget[0] === "window") { + document.body.appendChild(overlay); + if (this.appendTarget[1]) document.body.appendChild(focusBG); + } + inputField.focus(); + this.activeOverlays.push(overlay); + if (this.appendTarget[0] === "window") { + const resizeHandler = () => { + overlay.style.left = `${this.textBoxX !== null ? 50 + this.textBoxX : 50}%`; + overlay.style.top = `${this.textBoxY !== null ? 50 + this.textBoxY : 50}%`; + }; + document.addEventListener("fullscreenchange", resizeHandler); + document.addEventListener("webkitfullscreenchange", resizeHandler); + document.addEventListener("mozfullscreenchange", resizeHandler); + document.addEventListener("MSFullscreenChange", resizeHandler); + const observer = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === "childList" && Array.from(mutation.removedNodes).includes(overlay)) { + document.removeEventListener("fullscreenchange", resizeHandler); + document.removeEventListener("webkitfullscreenchange", resizeHandler); + document.removeEventListener("mozfullscreenchange", resizeHandler); + document.removeEventListener("MSFullscreenChange", resizeHandler); + observer.disconnect(); + } + } + }); + observer.observe(overlay.parentNode, { childList: true }); + document.body.appendChild(overlay); + } else { + if (this.appendTarget[1]) vm.renderer.addOverlay(focusBG, "scale-centered"); + vm.renderer.addOverlay(overlay, "scale-centered"); + } + inputField.focus(); + if (this.appendTarget[0] === "window") overlay.style.zIndex = "9999"; + else overlay.parentNode.style.zIndex = "9999"; + this.updateOverlay(overlay); + }); + } + } + closeOverlay(overlay, doneBefore) { + this.isDropdownOpen = false; + if (!doneBefore) { + this.askBoxInfo[0]--; + let usedBG = document.querySelectorAll(`div[class="SP-ask-boxBG"]`); + usedBG = usedBG[usedBG.length - 1]; + // ^ Prioritizes Textboxes on Window + const index = this.activeOverlays.indexOf(overlay); + setTimeout(() => { + if (index !== -1) { + this.activeOverlays.splice(index, 1); + this.askBoxPromises.splice(index, 1); + } + if (this.appendTarget[0] === "window") document.body.removeChild(overlay); + else vm.renderer.removeOverlay(overlay); + if (usedBG) { + if (usedBG.id === "window") document.body.removeChild(usedBG); + else vm.renderer.removeOverlay(usedBG); + } + }, this.Timeout * 1000); + } + } + + setButton(args) { + if (args.BUTTON === "add") { + this.buttonJSON[args.NAME] = { + borderRadius: 5, border: "1px none #000000", + color: "#0074D9", textColor: "#ffffff", + name: args.NAME, padding: "5px 10px", + image: "", imgScale: 100, + dropShadow: "none", outline: ["", 0] + }; + } else { delete this.buttonJSON[args.NAME] } + vm.extensionManager.refreshBlocks(); + } + + deleteAllButtons() { + this.buttonJSON = {}; + vm.extensionManager.refreshBlocks(); + } + + lastButton() { return this.lastPressBtn } + + isWaitingInput() { return this.activeOverlays.length > 0 } + + isDropdown() { return this.isDropdownOpen } + + setMaxBoxCount(args) { + this.askBoxInfo[1] = Scratch.Cast.toNumber(args.MAX); + if (this.askBoxInfo[1] > 1 && !Array.isArray(this.userInput)) this.userInput = [this.userInput]; + } + + setTimeout(args) { this.Timeout = Scratch.Cast.toNumber(args.TIME) } + + reportTimeout() { return this.Timeout } + + getUserInput() { + if (this.askBoxInfo[1] > 1) return this.userInput === null ? "[]" : JSON.stringify(this.userInput); + else return this.userInput === null ? "" : this.userInput; + } + + getBoxInfo(args) { + if (args.INFO.includes("button")) { + const buttons = Object.keys(this.buttonJSON); + return args.INFO.includes("names") ? JSON.stringify(buttons) : buttons.length; + } else { return this.askBoxInfo[args.INFO === "count" ? 0 : 1] } + } + + setSubmitEvent(args) { this.forceInput = args.ENTER } + + setDefaultV(args) { this.defaultValue = args.defaultV } + + setAppend(args) { this.appendTarget[0] = args.TARGET } + setFocus(args) { this.appendTarget[1] = args.TYPE === "Enabled" } + + setUI(args) { + let array; + try { array = JSON.parse(args.ARRAY.toLowerCase()) } catch { return } + if (!Array.isArray(array)) return; + const allowedUI = ["question", "input", "buttons"]; + let filteredArray = [...new Set(array.filter(element => allowedUI.includes(element)))]; + allowedUI.forEach(element => { + if (!filteredArray.includes(element)) filteredArray.push(element); + }); + this.uiOrder = filteredArray; + } + + getUIOrder() { return JSON.stringify(this.uiOrder) } + } + + Scratch.extensions.register(new BetterInputSP()); +})(Scratch); diff --git a/extensions/extensions.json b/extensions/extensions.json index 58a0294d03..d872c652da 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -56,6 +56,7 @@ "Lily/Cast", "-SIPC-/time", "-SIPC-/consoles", + "SharkPool/Better-Input", "ZXMushroom63/searchApi", "TheShovel/ShovelUtils", "Lily/Assets", diff --git a/images/SharkPool/Better-Input.svg b/images/SharkPool/Better-Input.svg new file mode 100644 index 0000000000..25557244fe --- /dev/null +++ b/images/SharkPool/Better-Input.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +