From dd9257e1b0c536a5d0eab1f8d2fa078fbc6589a5 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Fri, 30 Aug 2019 17:00:31 -0700 Subject: [PATCH] Field Text transformation methods (#2930) * Add field text transform methods for converting from text to value and vice versa. --- core/field_angle.js | 1 + core/field_textinput.js | 76 +++--- demos/custom-fields/icon.png | Bin 8733 -> 0 bytes demos/custom-fields/index.html | 197 ++++----------- demos/custom-fields/pitch/blocks.js | 33 +++ demos/custom-fields/pitch/field_pitch.js | 231 ++++++++++++++++++ demos/custom-fields/pitch/index.html | 103 ++++++++ demos/custom-fields/pitch/media/notes.png | Bin 0 -> 894 bytes demos/custom-fields/pitch/pitch.css | 27 ++ demos/custom-fields/{ => turtle}/blocks.js | 0 .../{ => turtle}/field_turtle.js | 0 demos/custom-fields/turtle/icon.png | Bin 0 -> 8733 bytes demos/custom-fields/turtle/index.html | 155 ++++++++++++ .../{ => turtle}/media/crown.svg | 0 .../{ => turtle}/media/fedora.svg | 0 .../custom-fields/{ => turtle}/media/mask.svg | 0 .../{ => turtle}/media/propeller.svg | 0 .../{ => turtle}/media/stovepipe.svg | 0 .../{ => turtle}/media/warning.svg | 0 .../{custom-fields.css => turtle/turtle.css} | 36 +-- demos/index.html | 2 +- 21 files changed, 664 insertions(+), 197 deletions(-) create mode 100644 demos/custom-fields/pitch/blocks.js create mode 100644 demos/custom-fields/pitch/field_pitch.js create mode 100644 demos/custom-fields/pitch/index.html create mode 100644 demos/custom-fields/pitch/media/notes.png create mode 100644 demos/custom-fields/pitch/pitch.css rename demos/custom-fields/{ => turtle}/blocks.js (100%) rename demos/custom-fields/{ => turtle}/field_turtle.js (100%) create mode 100644 demos/custom-fields/turtle/icon.png create mode 100644 demos/custom-fields/turtle/index.html rename demos/custom-fields/{ => turtle}/media/crown.svg (100%) rename demos/custom-fields/{ => turtle}/media/fedora.svg (100%) rename demos/custom-fields/{ => turtle}/media/mask.svg (100%) rename demos/custom-fields/{ => turtle}/media/propeller.svg (100%) rename demos/custom-fields/{ => turtle}/media/stovepipe.svg (100%) rename demos/custom-fields/{ => turtle}/media/warning.svg (100%) rename demos/custom-fields/{custom-fields.css => turtle/turtle.css} (74%) diff --git a/core/field_angle.js b/core/field_angle.js index f2cfe4b5e..059c17f53 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -134,6 +134,7 @@ Blockly.FieldAngle.prototype.initView = function() { /** * Updates the graph when the field rerenders. * @private + * @override */ Blockly.FieldAngle.prototype.render_ = function() { Blockly.FieldAngle.superClass_.render_.call(this); diff --git a/core/field_textinput.js b/core/field_textinput.js index 95db6533a..374bfee03 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -56,12 +56,6 @@ Blockly.FieldTextInput = function(opt_value, opt_validator) { } Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value, opt_validator); - /** - * A cache of the last value in the html input. - * @type {*} - * @private - */ - this.htmlInputValue_ = null; }; goog.inherits(Blockly.FieldTextInput, Blockly.Field); @@ -237,10 +231,10 @@ Blockly.FieldTextInput.prototype.showPromptEditor_ = function() { * @private */ Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) { - this.isBeingEdited_ = true; Blockly.WidgetDiv.show( this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this)); this.htmlInput_ = this.widgetCreate_(); + this.isBeingEdited_ = true; if (!quietInput) { this.htmlInput_.focus(); @@ -268,7 +262,7 @@ Blockly.FieldTextInput.prototype.widgetCreate_ = function() { htmlInput.style.borderRadius = borderRadius; div.appendChild(htmlInput); - htmlInput.value = htmlInput.defaultValue = String(this.value_); + htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_); htmlInput.untypedDefaultValue_ = this.value_; htmlInput.oldValue_ = null; if (Blockly.utils.userAgent.GECKO) { @@ -292,19 +286,11 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { // Finalize value. this.isBeingEdited_ = false; this.isTextValid_ = true; - // No need to call setValue because if the widget is being closed the - // latest input text has already been validated. - if (this.value_ != this.htmlInputValue_) { - // At the end of an edit the text should be the same as the value. It - // may not be if the input text is different than the validated text. - // There are two scenarios where that is the case: - // - The text in the input was invalid. - // - The text in the input is different to that returned by a validator. - // Re-render to fix that. - this.htmlInputValue_ = null; - this.forceRerender(); - } - // Otherwise don't rerender. + + // Always re-render when the we close the editor as value + // set on the field's node may be inconsistent with the field's + // internal value. + this.forceRerender(); // Call onFinishEditing // TODO: Get rid of this or make it less of a hack. @@ -384,7 +370,9 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) { // moved up to the Field setValue method. This would create a // broader fix for all field types. Blockly.Events.setGroup(true); - this.setEditorValue_(text); + var value = this.getValueFromEditorText_(text); + this.setValue(value); + this.forceRerender(); Blockly.Events.setGroup(false); } }; @@ -397,14 +385,15 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) { * @protected */ Blockly.FieldTextInput.prototype.setEditorValue_ = function(newValue) { - this.setValue(newValue); + this.isDirty_ = true; if (this.isBeingEdited_) { - this.htmlInput_.value = newValue; - // This cache is stored in order to determine if we must re-render when - // disposing of the widget div. - this.htmlInputValue_ = newValue; - this.forceRerender(); + // In the case this method is passed an invalid value, we still + // pass it through the transformation method `getEditorText` to deal + // with. Otherwise, the internal field's state will be inconsistent + // with what's shown to the user. + this.htmlInput_.value = this.getEditorText_(newValue); } + this.setValue(newValue); }; /** @@ -460,7 +449,7 @@ Blockly.FieldTextInput.numberValidator = function(text) { }; /** - * Ensure that only a nonnegative integer may be entered. + * Ensure that only a non-negative integer may be entered. * @param {string} text The user's text. * @return {?string} A string representing a valid int, or null if invalid. * @deprecated @@ -474,7 +463,7 @@ Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) { }; /** - * Use the `getText_` developer hook to override the field's text represenation. + * Use the `getText_` developer hook to override the field's text representation. * When we're currently editing, return the current html value instead. * Otherwise, return null which tells the field to use the default behaviour * (which is a string cast of the field's value). @@ -490,4 +479,31 @@ Blockly.FieldTextInput.prototype.getText_ = function() { return null; }; +/** + * Transform the provided value into a text to show in the html input. + * Override this method if the field's html input representation is different + * than the field's value. This should be coupled with an override of + * `getValueFromEditorText_`. + * @param {*} value The value stored in this field. + * @returns {string} The text to show on the html input. + * @protected + */ +Blockly.FieldTextInput.prototype.getEditorText_ = function(value) { + return String(value); +}; + +/** + * Transform the text received from the html input into a value to store + * in this field. + * Override this method if the field's html input representation is different + * than the field's value. This should be coupled with an override of + * `getEditorText_`. + * @param {string} text Text received from the html input. + * @returns {*} The value to store. + * @protected + */ +Blockly.FieldTextInput.prototype.getValueFromEditorText_ = function(text) { + return text; +}; + Blockly.fieldRegistry.register('field_input', Blockly.FieldTextInput); diff --git a/demos/custom-fields/icon.png b/demos/custom-fields/icon.png index 3a7314ac970453def5ae544be3c41722a44e2303..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 8733 zcmXY11yCGK69qzoOE}yiIEO=U2oB+JcXxMpx8N2mNN^8&xCJ@fo#5{7|9w?|)mqi= z%yjp5zkby{QHt_XXvl=fFfcG^GScG8(7P%0R7Cs)y`CdYp2NV9!^nt>sCs6f=6GeR z&ZQ1^IaGC&w}HJK!1-2$2_&e@mf4Y5I0#Csl5uPy+jPu{m&r1-rn8SNpjaBQY;nL? zf2k9;TmcrvPsAv~ILu&j^5<#UTXntXrz588!9v+Y$t*diaI(Dn{I(6h=Cii_b(e8L z9i%ifCsZkn{t0iG!CLN}e9j}8+YA5m=OSeK=`hYjSNQa;USq^I*~RPke0%XtF=4Xr z1`$32Vkd*FXduW=!{-t{wAXaDx4*?0uB{5^qYQ5#>KPQy}}|vCKoL#oa`w}XzV1$bMG`@8avt|TCS+|tCv;bKNUK<8hr1z3xcyMUpf;+~5Dyz6lcMEel6(!g=aFmnwu^K9^*!5J~$-X|z(CZgN$JB^!-C();(+-|GUM6f}X zX%g7tR7%D+8}k+$XkGlj6@mZUJcW<%pH@Xv#Np0h^wXPlxNoL6n1I>Q0GWiv>Bf&v zI;KSBrCVz4h(3$I0s|S!c48jc&hI+|jMO3i4E>n!K@Dleme`-e#1@9AMS5A$pcc=G zlIH{DKz}^8%u-1eCB66`CGTm$p)G`LU0gHhllY%0kvx9lS|EyqdV)BT7PiEdr2mGb zrZ{^d@06`8ncP1|5p3}*?_6Itg>IZ~PC2-77#MK=7es_F&BB59%YLBptWYBYsGht` zQk*sr>G-}7x=775iO!6Yy7td936W+dkx64|UosKjKe+v%Aur>crkhoF}G|{<4!)JsVnmU;Npf`!4R{veR{&g zTqIbLiE9o&mIwbxQ$%q%@eKKUB}w`+6GC+@qZ^F^ogM&;ruet{NSs-uDMJE_ib{mCW5wV`rbk{%~KAy4a}DZ?sGD8L8^Dd3$2rIsL%%}#>ioHclv z@^P_c@@dyX$Pqyf+2gZMWe4H%76r+B(dLryQoo4!0M;~y#?zz@gZVre>pVG5bKBB8 zfPQB-80c&A7Odf;n2GhP>d%p^MZ%>K z2$6yL;j`@HS@gMaCT`DD2Z_TnJPC| zC!znv6NaZ#UyOj;l1h8yF_XQHHxVu_caAgQnr7VOY}=&_W7g$<$>U25JL!O!B!o5K z(pOUB2h9OGWej_|LR)s0L3^gl-|W8TiI=aJw%P0+1U7v(t;ch1i|buEIu`5E3}w;< z;yGPE#F={v8OhAvZXQ!wM=&v4{FY8D&-E3;+Tb7!L%4rlwr>LZ>G`b~9JYuIo<5!5 zw@H#zA0vSv6WGw2Fy#oY=4Q^dXy`Dd7i&fo~+Gf zB@K$>D@qLFfFrhXw$w>cj>9{$nx03Z#;y2j>kRXu3m4sFWaNcrj`YqFYc!7;QN!o$xU=YWYpt%og9nmM_xA6g!@U_UNAAt#Cjp#Ub88Her5)OeSkZ9MWQECb=&i09kEa=4CmCW56cJ(MDgplhSd0N+Kgtg#^<_Xz(BEDJAkQq ztUa#Tqif9T-H){EWrd~eMm=eY9bOmKUhq|2y{X1@IwU!x%Ts8On|Q)&dt8j8fdcR$ zyhV&%`;(e-tL>4Q?a9&*i~4IY)U|OtS|jI3D(plJl{llRMGy8!^>hL~`p(&1WInEs3fW5`Mo(KW9bIULlPWd62@~Gk4*YY?-DXV<56O ztX4{^-`=TseN)n(e>)%B>mPAowpw{y2FDr)@0L5D4&`~|pulEx0}M|^W3;m8kR1I- zi}x1V<^GZi@%JL|5;T}5#9$NwLHet}m%P3=MLrj$q`_z8m%7;9p2b0$th=%|qS z1YjkW2owQUU$(|Zg^h&XeYb>N%#Slo8-X$Fn*B8k?}YF^+%g5B8=K8hRtG7(kRiNl z*Ag)71^cz8b1gDP2c(XSDPR^uOm9N(sZlt@@Vt4|A0hYDf?t9((j*`Oj6dMi?= ziVO8r&TFZ9GY%hn@zkVK)2<|-cgS^IJYGRbzxvp_|I`eq@clu5>?Yk#)*j~uv; zQ~fbK0lkk<=iZeHc>8>MdDk~lZ>mI-D2pi{PN2pX9h+w4a&8WUY(3TpO{6^0W25yAGA{Lu~K|eFuG|f8}~P8J6p(;Mrjkpk=AB2pC5F`y>Ecb z{#(Z0GadNFB2&O+aZKv3H?}b3|B!3B>Qg~%VlK6rrb03%USGIJTY{wCujow9$sp7U zdZH#;U+%O0FrcKUw4rv8D@Bqt0zoUuYv)wTlFr6($}7gQ%6eA{?}9 z!FeQ7rR6EKqSa)cBzxVN4`$OegN4&Sh-~7S%|P(20_qN>7_)$hrSQ>Ueewfz=hLW# ztDB!C!#FMi?B}25Z3Pm;40MN}zHhQrYzAc(1%EX{CN>!#aiPHJUqUv+&IbBW(-$Lk zYpCS|7^p>kkrx(cnwtl~eRQ**yV5#)C5co_E&f;gtt z-OEyl%6CjQ;P&X5>OJZV`(>HX*IRqaY*d#sr+w$MMVDc0j@8Z+cfKu>_Kj;Hxm`J3 zfk)%^^7P%6bSOD+IQPyPu)$N=ui2g+PIrcmspoIX(n(*p&ZE6@qM4W9ffRO)ZZ*be zfvxCOj!GPRLQyhw`7=uv1jhOt1VNbDyK9;#2QiFY9GqEs0|4KwA{;b)N=}NP(*cRT{Qg~fy#T97hnql!RB}R< z^y611AvEI_WYW>x@iQce8i3;qw2GE>;_Q!Zihbx^Qa9CC?RpFg7FiFeGy|U5WU^s{;n#6 zzrl`WO@oyhFRmLCqCSd=$iT53Qz6_{me0yBhppC z{hX*e?8jsKeUR04$iCTUk|>GXNK>+xGd|Yn68}>~CBxA_Zd(Fl)IFC4Ig%N6j2O4; zcLq}#q0-01`{oZI5D>ypHc&ubKtMR`ivr~)IJ`d{!l(8*~EM=0%lz=GCNzJQKl$WjN`0E8V z8yL^c%MHh?bOjM7I{c@psz~2zBq%)$rc+$ejOaW3*J$vb`~FyCaJ7%)u%)~jYi%#d zg6(B?$v*xTu{4_P^QrCYd4rhm4`t{$%1F35v^iVdj-bJ_S9%Aa9|<7j?_U}!nR=~^ za&lSr_>K3C?2F%kiwA)n_SQmmF#+%N5!!Fq5)9U&FFPA#dsiyVW&_g;rPVdXb*)Gu znD{&wKZ|AD#Lz0%^@pcR42r?kPmu$eBt38!_vZJxs~FQPmT=SKI8$+h%13-(g|3&JJ?p=8vlU-QIX6u$ix<&?I>Y{l%o!w!3pSp!UG*e$T$r5RRg1SU}4ryZXBoPDe*a{|r5iLiSMOg5IF8fzPSX zM5RGTZbePj#NPc^wtGewB=t}6(JJkwbjTrk<>izzNuE?lSDWA9@>ZlK$*8aqKQo{x zov{pVH*EI^(O<}=i1WF-Ixn{d4DPVVQ)i7kEs%KBdXH_~QAkQjA*Ad2vdxr`Db&&o z#n|X?t>^jUDPav4V*taCr2i!ggiGy+A=HPB_?pp0ivj^(h|y+<4yjXRD99D}7z^tQ zl76vBhjj(JGRyH<(9WXLgre!u62>_%$!T1KJma!i9Tjz)*?7feIlS>r2wbD?=LHB& znG*Mz)aE;U>DVM~2o5Lrzw3Qf;mSb`T~mEvJW+hRP+!-5rqOY?rLfqk)x`zpn_#2H zHUtgs<`)zMU&?Lsx-FN@YAgU0`ek;Ne-kIbMixjn(2itm53&|}kLur(KE8cfeLa8w z(LCMfklq2Zvm@zxUppt@^25Ft@ZwI-@h3=@lq=w#f3}hz{Pi%4Xsk_BUa735Km6kd z3DD1i{@mUteZ5Iv03AcVBssbmnL$be8mZbMg$az96a89EMg#%c&$RNS9_%jT8Ddp! zX=iU|eyX}!Niww4u4*1^*_#572LRyHI)>)j8?#Dv#^?rVV^!A&e%l@rC`b8PZm}S02;F*<8xTWWJ|9hPsD)?loFimfCmkIwmgM$pybQ zIQC-@6q{`+>%j}a`aiZAT=m;1C@v|>9A~K0ot42FXMBCf{guEFXqd$hTI-*ctG@)= zM`W>?4A%NV-0cJYxvM+q$@!`I4f`CTJPZ~8(Y^i6*z#J@?7WGCciE@$7BkOla@it4 znY_rRNunpIH-;hYtYxjKK0zd~gss61)`r z<-%hfD*tPxvFgX%gu$;Xk#K276vJ5c3;=7iO;nyKXF|Cux!K0~%b9flk>WeKS$G?1 zYGs}d`Mq^dp@78~!i9~-z{;fG`M+WHX4pO^?}eK;*{<{gWb?jVpbVwOZmBNTJE8QA zL`+RDDo^hSF%q*882_Z<>LHxBgJP`uTX=CA4HiFE5hcB@KVHusIeJaVk<*+d0h$t) zl3=;s7)26I6MuL_qQJ`aw72u9ob#(+Y=waU*3F2G^DVv#gu zI=P9dCH_nx%Re!Q)lfM|D7mDjIo?n%0i+Q=-k76lAG7Yr@?}KQaqgrdK_WB7|6k^x z2!K>9x%`~W6y4QyoO_#)GJ_L^+Aj++jBs;u zdMrPkCsw&+McEy}10>Nn3US$@@@nL{eB&b;ZIQlBcg#5P z!utm!_# zOCF**P`C@&>ksUc#)L2zgiV|lu4;^vz28aU)@Xumhpe&FnSgRL;z`u81tf%D7%0;o zgD@L`khd(eR5*OpHE1zVTxJy#%I>J{=t*Mw#3Ak>i4=#b#2I5L5!d-c$eYi{@`B58 zjCz69Xcqoy`}IN4U(}2OweOcAaFVDQBSb1j?YS1SzaA(ckKrGmx9-Wp((#5>Q|*rv zFr>hW4W&SEeQ^Em19aN%N3NEtr41<9&2VWX8oO0DNmb)XM|C9f^*57vG=$zBC*1yn0ZrenY|P2{}32!%Vg^H8tsueY2GBMj13^scglLqt1`0>xHa zmtKk=Q(f6#qu>cHW*DP`j}(WC?wPioc*xIdW)z-kgGe^-A1+({ZU&w_H{)4qU)FYa z<^+01$jy7jAuFOhi^TnhE=dY|PN}Vq5vz#7-zfCR0mJ0OSnJj-^(w{Iwm8+lPy}nX ztM0atTUgPObp@oD)5LB|Jw-{g{b?HkHck&D{a_Q&t^97w<)D(9zJy$9_%C5;aDw^q zc-4VK&fe5m|C0&6M94SRZw!f)?ff7WSpc{i<0-e;s(??Gfq^mdMto@h+LS{}bujS~ z6q#m*GSkCsIH~kAEsByJ8orTKltBH?41`>tz;y2R*qY(NPyj7?Ce~}))1m%?-gGR~ z$g}d50|w_(?n66Uyx;{lzzV^adM?q8Vj<{XcB=^07M+zZ9RS>rk5C@wa;_bNqt1WK zwB@IJ>JW=yE3PrzsvIYZu>-Pbou>}*rc@U~tukG2J@R$WvEf2sw1z;iht>ewaUt4ltR)T~Ro~N8ui~aW8zbmK;BLht3P=eY76iKel`(X`6MrsEy`f?hhGG9$jlj)h%J`r{Z#U-V^@D1 zd;uKKbVT0|*!4|1q}l+V>u72g%~&zmFgSs;8=4ze?zox(eXNUMBgXjEd&H7#FJx~e z+G&!WTGVpu3OZ{EK7KwcdpnxXa+v4wUlbMbvN$!(*&peyDo__`*gT5k>H_&qbVr8xK7#YI11J-4x@{qlm7@Y zN)>{*X2uFwQ6-t$h_v~*uShJwPfim~iKlow2%1R6QtJ$zam?T4oC06c!+?JF^!Mw| zEF&H``kqH0)2j48b@wvjB^@XvjG4AtZ>o)Eam4G`gTve^=Gm;CsB}ms@H`I>(=CaY zq<;x~Gm-n^(D8_ivHAvM*2!*~KFV~zE=rD1p2PRhxBDg-ULZaNloYD`h(>NOR5-%G zof}Xnm`LVCt_7vvwLfue)Gu~)6qFaVBg(S+3!PGmdNn5A81&=u#-_i|;eAi;8uY*2 z6di!J*UAfWKHK!%;|1jDm!+4FFo61~>M455s!G^hMnKEnD|cpOxD663o(~HcE_3NM z#`CjUFA%}^UM-ag<2&jdGGFWJ7VNMftyH@Bth)O4m^cRG`9yev34Kej_!ufzGhPMYIMk_u%>HQmUika z_YBwhL~08+lb2riqo~-CrVlwKdwym7;bMf;KDzLw|HZ2O@Tc5dB+oz*7l&ZZEbvEXMr|v_k22JvmSPM5pqpvi$}Oae*a9~*pJP=bJLtki;@fiIF6@a ztnVmAhr4=An3%o;D4=M zkXYKGf}xD^ri#dpiR7iJ0u%Znp6BH7#AyEGA4eV%*OQZPDdLoZpR3>RZkwVcm85Bs z+MBlptKY|OcU-*q`1zGg!~VkxS3O$NIG2%1mBm&Y+MZ}~457zgcKxnA2S*2#snQgX zh+lW4H_2k(qDo>?Xf7Oi0>3jl25urh&vO%?MwdgOA>IGeitbEjpUzWHsJAfvzkoeFCvrr*qX!VL^h89}N1`h8;^R6%7;-<@Y zrf3_S(yFh_%H=3=6+VbU@V;wDkS{Zsa1scXiuQd-_WN7Jp@`EsTSFyNr`jDjRh@1`=nMpQ4OIFupOYODLYl1HB%{pmsruDJ<)Tw4k-~9RN+4 z*JPID`CKf#f)O}z_{Cwzir%KRMp2Db-J`0Lw!(Bbh{UyF&zmd`lRD9p;YYE}+y|jz zSl;ZB-*oqB9Qjtg>-!1N&f{*i^K+KDvLF?TOwuD&nxT{EE7E=P_L5zqokfZlL^7-R zuz%~%gyUm-H%$7%@7a_E;27SurshrqF)r?$*&N6++i+p|iKjzQO}dV^KJ(V@n4Svd zrsOHAxUgZiQE?J^P;()uJ6M-ZN>&f0Pqc4nha=RNB3uK#WK>1n`mU}#X<~Ex;c46I ziu-cC`ks{!t@n~uFe*2mtA?!apY#satokh0RhVU!)$d-7I)$wGHHwgY+I5ovt}rT+ znFe%BaQ?QN7n1jV-j{%Gc;4_YC - - - - Blockly Demo: Custom Fields - - - - - - - -

Blockly > - Demos > Custom Fields

+ + + + Blockly Demos + + + +

Blockly > + Demos > Custom Fields

+

These demos are intended for developers who want creating custom block fields.

-

This is a demo of creating custom block fields. In this case the field - is used to define a turtle. -

+ + + + + -

All of the custom field implementation is in - demos/custom-fields/field_turtle.js, including comments on each required - function. -

- -

Click on the blocks' comment icons to learn what they are demonstrating. - Use the buttons below to see how the fields react to changes. -

- -

- - - - - -

- -

- - -

- -
+ + + + + +
- - - - -
- - -
-
- - - - - + + + + + + + + + + + + diff --git a/demos/custom-fields/pitch/blocks.js b/demos/custom-fields/pitch/blocks.js new file mode 100644 index 000000000..bfa300424 --- /dev/null +++ b/demos/custom-fields/pitch/blocks.js @@ -0,0 +1,33 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2019 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Pitch field demo blocks. + * @author samelh@gmail.com (Sam El-Husseini) + */ + +Blockly.Blocks['test_pitch_field'] = { + init: function() { + this.appendDummyInput() + .appendField('pitch') + .appendField(new Blockly.FieldPitch('7'), 'PITCH'); + this.setStyle('loop_blocks'); + } +}; diff --git a/demos/custom-fields/pitch/field_pitch.js b/demos/custom-fields/pitch/field_pitch.js new file mode 100644 index 000000000..3a50237ac --- /dev/null +++ b/demos/custom-fields/pitch/field_pitch.js @@ -0,0 +1,231 @@ +/** + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Music pitch input field. Borrowed from Blockly Games. + * @author fraser@google.com (Neil Fraser) + * @author samelh@google.com (Sam El-Husseini) + */ +'use strict'; + +goog.provide('Blockly.FieldPitch'); + +goog.require('Blockly.FieldTextInput'); +goog.require('Blockly.utils.math'); + + +/** + * Class for an editable pitch field. + * @param {string} text The initial content of the field. + * @extends {Blockly.FieldTextInput} + * @constructor + */ +Blockly.FieldPitch = function(text) { + Blockly.FieldPitch.superClass_.constructor.call(this, text); +}; +goog.inherits(Blockly.FieldPitch, Blockly.FieldTextInput); + +/** + * Construct a FieldPitch from a JSON arg object. + * @param {!Object} options A JSON object with options (pitch). + * @return {!Blockly.FieldPitch} The new field instance. + * @package + * @nocollapse + */ +Blockly.FieldPitch.fromJson = function(options) { + return new Blockly.FieldPitch(options['pitch']); +}; + +/** + * All notes available for the picker. + */ +Blockly.FieldPitch.NOTES = 'C3 D3 E3 F3 G3 A3 B3 C4 D4 E4 F4 G4 A4'.split(/ /); + +/** + * Show the inline free-text editor on top of the text and the note picker. + * @private + */ +Blockly.FieldPitch.prototype.showEditor_ = function() { + Blockly.FieldPitch.superClass_.showEditor_.call(this); + + var div = Blockly.WidgetDiv.DIV; + if (!div.firstChild) { + // Mobile interface uses Blockly.prompt. + return; + } + // Build the DOM. + var editor = this.dropdownCreate_(); + Blockly.DropDownDiv.getContentDiv().appendChild(editor); + + var border = this.sourceBlock_.getColourBorder(); + border = border.colourBorder || border.colourLight; + Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), border); + + Blockly.DropDownDiv.showPositionedByField( + this, this.dropdownDispose_.bind(this)); + + // The note picker is different from other fields in that it updates on + // mousemove even if it's not in the middle of a drag. In future we may + // change this behaviour. For now, using bindEvent_ instead of + // bindEventWithChecks_ allows it to work without a mousedown/touchstart. + this.clickWrapper_ = + Blockly.bindEvent_(this.imageElement_, 'click', this, + this.hide_); + this.moveWrapper_ = + Blockly.bindEvent_(this.imageElement_, 'mousemove', this, + this.onMouseMove); + + this.updateGraph_(); +}; + +/** + * Create the pitch editor. + * @return {!Element} The newly created pitch picker. + * @private + */ +Blockly.FieldPitch.prototype.dropdownCreate_ = function() { + this.imageElement_ = document.createElement('div'); + this.imageElement_.id = 'notePicker'; + + return this.imageElement_; +}; + +/** + * Dispose of events belonging to the pitch editor. + * @private + */ +Blockly.FieldPitch.prototype.dropdownDispose_ = function() { + Blockly.unbindEvent_(this.clickWrapper_); + Blockly.unbindEvent_(this.moveWrapper_); +}; + +/** + * Hide the editor. + * @private + */ +Blockly.FieldPitch.prototype.hide_ = function() { + Blockly.WidgetDiv.hide(); + Blockly.DropDownDiv.hideWithoutAnimation(); +}; + +/** + * Set the note to match the mouse's position. + * @param {!Event} e Mouse move event. + */ +Blockly.FieldPitch.prototype.onMouseMove = function(e) { + var bBox = this.imageElement_.getBoundingClientRect(); + var dy = e.clientY - bBox.top; + var note = Blockly.utils.math.clamp(Math.round(13.5 - dy / 7.5), 0, 12); + this.imageElement_.style.backgroundPosition = (-note * 37) + 'px 0'; + this.setEditorValue_(note); +}; + +/** + * Convert the machine-readable value (0-12) to human-readable + * text (C3-A4). + * @param {number|string} value The provided value. + * @return {string} The respective note. + */ +Blockly.FieldPitch.prototype.valueToNote = function(value) { + var note = Blockly.FieldPitch.NOTES[Number(value)]; + return note; +}; + +/** + * Convert the human-readable text (C3-A4) to machine-readable + * value (0-12). + * @param {string} text The provided note. + * @return {number} The respective value. + */ +Blockly.FieldPitch.prototype.noteToValue = function(text) { + var i = Blockly.FieldPitch.NOTES.indexOf(text); + return i; +}; + +/** + * Get the text to be displayed on the field node. + * @return {?string} The HTML value if we're editing, otherwise null. Null means + * the super class will handle it, likely a string cast of value. + * @protected + */ +Blockly.FieldPitch.prototype.getText_ = function() { + if (this.isBeingEdited_) { + return Blockly.FieldPitch.superClass_.getText_.call(this); + } + return this.valueToNote(this.getValue()) || null; +}; + +/** + * Transform the provided value into a text to show in the HTML input. + * @param {*} value The value stored in this field. + * @returns {string} The text to show on the HTML input. + */ +Blockly.FieldPitch.prototype.getEditorText_ = function(value) { + return this.valueToNote(value); +}; + +/** + * Transform the text received from the HTML input (note) into a value + * to store in this field. + * @param {string} text Text received from the HTML input. + * @returns {*} The value to store. + */ +Blockly.FieldPitch.prototype.getValueFromEditorText_ = function(text) { + return this.noteToValue(text); +}; + +/** + * Updates the graph when the field rerenders. + * @private + * @override + */ +Blockly.FieldPitch.prototype.render_ = function() { + Blockly.FieldPitch.superClass_.render_.call(this); + this.updateGraph_(); +}; + +/** + * Redraw the note picker with the current note. + * @private + */ +Blockly.FieldPitch.prototype.updateGraph_ = function() { + if (!this.imageElement_) { + return; + } + var i = this.getValue(); + this.imageElement_.style.backgroundPosition = (-i * 37) + 'px 0'; +}; + +/** + * Ensure that only a valid value may be entered. + * @param {*} opt_newValue The input value. + * @return {*} A valid value, or null if invalid. + */ +Blockly.FieldPitch.prototype.doClassValidation_ = function(opt_newValue) { + if (opt_newValue === null || opt_newValue === undefined) { + return null; + } + var note = this.valueToNote(opt_newValue); + if (note) { + return opt_newValue; + } + return null; +}; + +Blockly.fieldRegistry.register('field_pitch', Blockly.FieldPitch); diff --git a/demos/custom-fields/pitch/index.html b/demos/custom-fields/pitch/index.html new file mode 100644 index 000000000..4dd557747 --- /dev/null +++ b/demos/custom-fields/pitch/index.html @@ -0,0 +1,103 @@ + + + + + Blockly Demo: Custom Pitch Field + + + + + + + +

+ Blockly > + Demos > + Custom Fields > Pitch Field

+ + +

This is a demo of creating custom block fields. In this case the field + is used to select a note pitch. +

+ +

All of the custom field implementation is in + demos/custom-fields/pitch/field_pitch.js, including comments on each required + function. +

+ +

+ + +

+ + + + + + +
+ + +
+
+ + + + + + diff --git a/demos/custom-fields/pitch/media/notes.png b/demos/custom-fields/pitch/media/notes.png new file mode 100644 index 0000000000000000000000000000000000000000..b9a57b59eca4c7dc8cd55c242dcd7fc60004b370 GIT binary patch literal 894 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 + + + + Blockly Demo: Custom Turtle Field + + + + + + + +

+ Blockly > + Demos > + Custom Fields > Turtle Field

+ + +

This is a demo of creating custom block fields. In this case the field + is used to define a turtle. +

+ +

All of the custom field implementation is in + demos/custom-fields/turtle/field_turtle.js, including comments on each required + function. +

+ +

Click on the blocks' comment icons to learn what they are demonstrating. + Use the buttons below to see how the fields react to changes. +

+ +

+ + + + + +

+ +

+ + +

+ + + + + + +
+ + +
+
+ + + + + + diff --git a/demos/custom-fields/media/crown.svg b/demos/custom-fields/turtle/media/crown.svg similarity index 100% rename from demos/custom-fields/media/crown.svg rename to demos/custom-fields/turtle/media/crown.svg diff --git a/demos/custom-fields/media/fedora.svg b/demos/custom-fields/turtle/media/fedora.svg similarity index 100% rename from demos/custom-fields/media/fedora.svg rename to demos/custom-fields/turtle/media/fedora.svg diff --git a/demos/custom-fields/media/mask.svg b/demos/custom-fields/turtle/media/mask.svg similarity index 100% rename from demos/custom-fields/media/mask.svg rename to demos/custom-fields/turtle/media/mask.svg diff --git a/demos/custom-fields/media/propeller.svg b/demos/custom-fields/turtle/media/propeller.svg similarity index 100% rename from demos/custom-fields/media/propeller.svg rename to demos/custom-fields/turtle/media/propeller.svg diff --git a/demos/custom-fields/media/stovepipe.svg b/demos/custom-fields/turtle/media/stovepipe.svg similarity index 100% rename from demos/custom-fields/media/stovepipe.svg rename to demos/custom-fields/turtle/media/stovepipe.svg diff --git a/demos/custom-fields/media/warning.svg b/demos/custom-fields/turtle/media/warning.svg similarity index 100% rename from demos/custom-fields/media/warning.svg rename to demos/custom-fields/turtle/media/warning.svg diff --git a/demos/custom-fields/custom-fields.css b/demos/custom-fields/turtle/turtle.css similarity index 74% rename from demos/custom-fields/custom-fields.css rename to demos/custom-fields/turtle/turtle.css index 5162e0392..40af25e94 100644 --- a/demos/custom-fields/custom-fields.css +++ b/demos/custom-fields/turtle/turtle.css @@ -19,44 +19,44 @@ */ .customFieldsTurtleWidget { - width: 150px; + width: 150px; } .customFieldsTurtleWidget button { - border-radius: 4px; - border: none; - background-color: #fff; - opacity: .6; - color: #000; + border-radius: 4px; + border: none; + background-color: #fff; + opacity: .6; + color: #000; } .customFieldsTurtleWidget .table { - width: 100%; + width: 100%; } .customFieldsTurtleWidget .row { - width: 100%; - display: flex; + width: 100%; + display: flex; } .customFieldsTurtleWidget .arrow { - text-align: center; - padding: 0; - flex-grow: 0; + text-align: center; + padding: 0; + flex-grow: 0; } .customFieldsTurtleWidget .text { - height: 20px; - color: #fff; - flex-grow: 1; - text-align: center; + height: 20px; + color: #fff; + flex-grow: 1; + text-align: center; } .customFieldsTurtleWidget .randomize { - width: 100%; + width: 100%; } .blocklyNonEditableText text, .blocklyEditableText text { - fill: #000; + fill: #000; } diff --git a/demos/index.html b/demos/index.html index 75328a512..eb06f3d7c 100644 --- a/demos/index.html +++ b/demos/index.html @@ -152,7 +152,7 @@ - +